Python 开发指南:元编程、访问拦截器

2022-09-1811:41:01编程语言入门到精通Comments1,237 views字数 1965阅读模式

基于 Python 动态执行的特性,一个类的实例应当有哪些属性 ( field ) 和方法 ( 这些定义被称之元信息 ),并不像其它编译型语言那样在程序运行之前就确定不变了,而可能是随着脚本的运行而被临时修改甚至创建。换句话说,Python 可以在运行时随时修改类或实例的元信息,简称元编程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

元编程极大地拓展了脚本语言的灵活性,你或许还可以从另一门 Groovy 语言的 MOP 元对象协议中获取一些有意思的灵感。见:一文通读 Groovy 元对象协议 MOP - 掘金 (juejin.cn)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

元信息检查

首先从元信息检查开始说起。这可以通过两个内置函数进行:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

  1. vars() 函数能以字典形式打印出某个实例所具有的属性。
  2. dir() 函数能以列表形式打印出某个实例的所有属性以及方法 ( 包括从类 object 获取的魔法函数 ) 的标识符。比如:
class Foo:
    def __init__(self,v_):
        self.v = v_

    def f(self):pass

    @staticmethod
    def g():pass

foo = Foo(1)
print(dir(foo))  # ['__class__', '__delattr__', ... , 'f', 'g', 'v']
print(vars(foo))  # {'v': 1}

除此之外,Python 对象内部通用内置了属性或方法来反射元信息:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

  1. foo.__dict__ 属性,相当于调用 vars(foo)
  2. foo.__dir__() 方法,相当于调用 dir(foo)

使用 hasattr() 函数可以检测查看某个对象的元信息内是否包含某个标识符。若要动态获取标识符,可以使用 getattr() 函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

print(hasattr(foo,"f"))
print(getattr(foo,"f"))

如果 getattr() 函数返回的标识符是属性,那么该方法会直接返回它的值或引用。若返回的标识符是方法,则可以将它看作一个可调用对象 ( 它是 method 类型 ) 调用,如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

foo = Foo(1)
invokable = getattr(foo,"f")
invokable()

Python 的 inspect 模块提供了内置的 ismethod() 函数,用于检测该标识符是否为方法。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

mthd = inspect.ismethod(getattr(foo,"f"))
print(mthd)  # True
mthd()

元信息注入

使用 setattr() 函数可以向已构造的对象内部插入新的属性值。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

foo = Foo(1)
setattr(foo, "a", 2)
print(getattr(foo,"a"))  # 2

或者直接以硬编码的方式注入属性。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

class Goo: pass

goo1 = Goo()
goo1.i = 20

print(goo1.i)

通过 MethodType 可以直接将表达式注入对象内作为方法。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

goo1 = Goo()

#  保留第一个参数作为方法接收者。
def method_(this): print(this.i)

#  lambda 表达式也需要保留第一个参数作为方法接收者
lambda_ = lambda this: print(this.i)

goo1.invocable = MethodType(lambda_, goo1)
goo1.invocable()

不难理解,如果方法直接注入到 Goo 类型内,则该方法将作为类方法。此时,表达式的 this 将指代 cls 而非 self文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

访问拦截器

Python 对象内置了 __getattr__()__getattribute__()__setattr__() 三个魔法函数:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

  1. 当访问 尚未定义的属性或方法 时,该行为会被对象的 __getattr__() 方法拦截。
  2. 无论访问的属性或方法是否定义,该行为总会被对象的 __getattribute__() 方法拦截。在 __getattribute__()__getattr__() 同时被重写的场合,优先调用前者。当前者抛出 AttributeError 时,再尝试访问后者。
  3. 向一个程序注入属性或方法时,该行为总会被该对象的 __setattr__() 方法拦截。

通过合理利用这三个魔法函数,我们可以创建出一个安全的,可被安全访问的动态对象。这里举一个简单的例子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

class Foo:

    def __init__(self):
        self.v_ = 10

    def __getattribute__(self, item):
        try:
            v = object.__getattribute__(self, item)
        except AttributeError:
            return None
        return v
    
foo = Foo()
foo.a = 100
print(foo.a)  #  100
print(foo.b)  #  None

基于这种实现,当访问未定义的属性时,Foo 实例将以返回 None 的形式代替抛出 AttributeError文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

作者:花花子
来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27841.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/27841.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定