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

2023年 7月 12日 45.1k 0

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

元编程极大地拓展了脚本语言的灵活性,你或许还可以从另一门 Groovy 语言的 MOP 元对象协议中获取一些有意思的灵感。见:一文通读 Groovy 元对象协议 MOP - 掘金 (juejin.cn)。

元信息检查

首先从元信息检查开始说起。这可以通过两个内置函数进行:

  • vars() 函数能以字典形式打印出某个实例所具有的属性。
  • 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 对象内部通用内置了属性或方法来反射元信息:

  • foo.__dict__ 属性,相当于调用 vars(foo)
  • foo.__dir__() 方法,相当于调用 dir(foo)
  • 使用 hasattr() 函数可以检测查看某个对象的元信息内是否包含某个标识符。若要动态获取标识符,可以使用 getattr() 函数。

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

    如果 getattr() 函数返回的标识符是属性,那么该方法会直接返回它的值或引用。若返回的标识符是方法,则可以将它看作一个可调用对象 ( 它是 method 类型 ) 调用,如:

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

    Python 的 inspect 模块提供了内置的 ismethod() 函数,用于检测该标识符是否为方法。比如:

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

    元信息注入

    使用 setattr() 函数可以向已构造的对象内部插入新的属性值。比如:

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

    或者直接以硬编码的方式注入属性。比如:

    class Goo: pass
    
    goo1 = Goo()
    goo1.i = 20
    
    print(goo1.i)
    

    通过 MethodType 可以直接将表达式注入对象内作为方法。比如:

    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

    访问拦截器

    Python 对象内置了 __getattr__()__getattribute__()__setattr__() 三个魔法函数:

  • 当访问 尚未定义的属性或方法 时,该行为会被对象的 __getattr__() 方法拦截。
  • 无论访问的属性或方法是否定义,该行为总会被对象的 __getattribute__() 方法拦截。在 __getattribute__()__getattr__() 同时被重写的场合,优先调用前者。当前者抛出 AttributeError 时,再尝试访问后者。
  • 向一个程序注入属性或方法时,该行为总会被该对象的 __setattr__() 方法拦截。
  • 通过合理利用这三个魔法函数,我们可以创建出一个安全的,可被安全访问的动态对象。这里举一个简单的例子:

    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

    作者:花花子 来源:稀土掘金

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论