Python Descriptors -> 对数据进行校验

    xiaoxiao2025-04-06  30

    描述器的表现

    用到3个魔术方法 : _get_()、_set_()、_delete_()

    方法签名如下 :

    object.get(self, instance, owner)object.set(self, instance, value)object.delete(self, instance)

    self 指代当前实例, 调用者 instance 是owner的实例 owner 是属性的所属的类

    用例子来看

    class A: def __init__(self): self.a1 = 'a1' print('A.init') class B: x = A() def __init__(self): print('B.init') print(B.x.a1) b = B() print(b.x.a1)

    运行结果

    A.init a1 B.init a1

    可以看出执行的先后顺序吧

    类加载的时候, 类变量需要先生成而类B 的x 属性是类A 的实例, 所以打印A.init然后执行到B.x.a1接着实例化并初始化B的实例b打印b.x.a1, 会查找类属性b.x, 指向A的实例, 所以返回A实例的属性a1的值

    看懂执行流程后在看下面的程序, 对类A做一些改造, 如果在对类A中实现__get__方法, 看变化 …

    class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print("A.__get__{}, {}, {}".format(self, instance, owner)) class B: x = A() def __init__(self): print('B.init') print(B.x) # print(B.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1' b = B() print(b.x) # print(b.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'

    运行结果

    A.init A.__get__<__main__.A object at 0x00000157B0658278>, None, <class '__main__.B'> None B.init A.__get__<__main__.A object at 0x00000157B0658278>, <__main__.B object at 0x00000157B0658898>, <class '__main__.B'> None

    因为定义了__get__ 方法, 类A 就是一个描述器, 对类B 或者类B 的实例的x 属性读取, 成为对类A 的 实例的访问, 就会调用__get__方法

    如何解决上例中访问报错的问题, 问题来自__get__方法

    self, instance, owner 这三个参数是什么意思 ?

    – self 都是A 的实例 – instance 都是B 类 – owner 说明

    <__main__.A object at 0x00000157B0658278>, <__main__.B object at 0x00000157B0658898>, <class '__main__.B'>

    针对上面的问题 2, 使用返回值解决, 返回self, 就是A 的实例, 该实例有a1 属性, 返回正常.

    class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print("A.__get__{}, {}, {}".format(self, instance, owner)) return self class B: x = A() def __init__(self): print('B.init')

    此处省略打印结果 …


    那么类B 的实例属性也可以 ?

    class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print("A.__get__{}, {}, {}".format(self, instance, owner)) return self class B: x = A() def __init__(self): print('B.init') self.b = A() # 实例属性指向一个A 的实例 print(B.x) print('-'*50) print(B.x.a1) print('-'*50) b = B() print(b.x) print('-'*50) print(b.x.a1) print('-'*50) print(b.b) # 没有触发__get__

    运行结果

    A.init A.__get__<__main__.A object at 0x0000020EB6998278>, None, <class '__main__.B'> <__main__.A object at 0x0000020EB6998278> -------------------------------------------------- A.__get__<__main__.A object at 0x0000020EB6998278>, None, <class '__main__.B'> a1 -------------------------------------------------- B.init A.init A.__get__<__main__.A object at 0x0000020EB6998278>, <__main__.B object at 0x0000020EB6998940>, <class '__main__.B'> <__main__.A object at 0x0000020EB6998278> -------------------------------------------------- A.__get__<__main__.A object at 0x0000020EB6998278>, <__main__.B object at 0x0000020EB6998940>, <class '__main__.B'> a1 -------------------------------------------------- <__main__.A object at 0x0000020EB6998AC8> 从运行结果可以看出, 只有类属性是类的实例才行

    描述器定义

    Python 中, 一个类实现了_get_、_set_、_delete_ 三个方法中任何一个方法就是描述器如果实现了__get__ 就是 非数据描述符 non-data descriptor实现了__get__、__set__就是 数据描述符 data descriptor如果一个类的雷属性设置为描述器, 那么它被称为 owner属主

    属性访问顺序

    继续用例子来看

    class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print("A.__get__{}, {}, {}".format(self, instance, owner)) return self class B: x = A() def __init__(self): print('B.init') self.x = 'b.x' # 增加实例属性 x print(B.x) print('-'*50) print(B.x.a1) print('-'*50) b = B() print(b.x) print('-'*50) # print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'

    运行结果

    A.init A.__get__<__main__.A object at 0x000001B1A9D28940>, None, <class '__main__.B'> <__main__.A object at 0x000001B1A9D28940> -------------------------------------------------- A.__get__<__main__.A object at 0x000001B1A9D28940>, None, <class '__main__.B'> a1 -------------------------------------------------- B.init b.x -------------------------------------------------- b.x 访问到了实例的属性, 而不是描述器

    继续修改代码, 为类A 增加__set__ 方法

    class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print("A.__get__{}, {}, {}".format(self, instance, owner)) return self def __set__(self, instance, value): print("A.__set__{}, {}, {}".format(self, instance, value)) self.data = value class B: x = A() def __init__(self): print('B.init') self.x = 'b.x' # 增加实例属性 x print(B.x) print('-'*50) print(B.x.a1) print('-'*50) b = B() print(b.x) print('-'*50)

    运行结果

    A.init A.__get__<__main__.A object at 0x000001E1AA558940>, None, <class '__main__.B'> <__main__.A object at 0x000001E1AA558940> -------------------------------------------------- A.__get__<__main__.A object at 0x000001E1AA558940>, None, <class '__main__.B'> a1 -------------------------------------------------- B.init A.__set__<__main__.A object at 0x000001E1AA558940>, <__main__.B object at 0x000001E1AA558B00>, b.x A.__get__<__main__.A object at 0x000001E1AA558940>, <__main__.B object at 0x000001E1AA558B00>, <class '__main__.B'> <__main__.A object at 0x000001E1AA558940> -------------------------------------------------- 返回变成了a1 , 访问到了描述器的数据

    属性查找顺序

    实例的__dict__优先于 非数据描述器非数据描述器优先于实例的__dict___delete_ 方法有同样的效果, 有了这个方法就是 数据描述器

    对实例数据校验

    最初实现

    class TypeCheck: def __init__(self, name, typ): self.name = name self.type = typ def __get__(self, instance, owner): print('get') if instance: return instance.__dict__[self.name] return self def __set__(self, instance, value): print('set') if not isinstance(value, self.type): raise TypeError instance.__dict__[self.name] = value class Person: name = TypeCheck('name', str) # 硬编码 age = TypeCheck('age', int) # 不优雅 def __init__(self, name:str, age:int): self.name = name self.age = age

    装饰器实现

    import inspect class TypeCheck: def __init__(self, name, typ): self.name = name self.type = typ def __get__(self, instance, owner): print('get') if instance: return instance.__dict__[self.name] return self def __set__(self, instance, value): print('set') if not isinstance(value, self.type): raise TypeError instance.__dict__[self.name] = value def typeinject(cls): sig = inspect.signature(cls) params = sig.parameters for name, param in params.items(): print(name, param) if param.annotation != param.empty: setattr(cls, name, TypeCheck(name, param.annotation)) return cls @typeinject class Person: # name = TypeCheck('name', str) # 硬编码 # age = TypeCheck('age', int) # 不优雅 def __init__(self, name:str, age:int): self.name = name self.age = age

    描述器实现

    import inspect class TypeCheck: def __init__(self, name, typ): self.name = name self.type = typ def __get__(self, instance, owner): print('get') if instance: return instance.__dict__[self.name] return self def __set__(self, instance, value): print('set') if not isinstance(value, self.type): raise TypeError instance.__dict__[self.name] = value class TypeInject: def __init__(self, cls): self.cls = cls sig = inspect.signature(cls) params = sig.parameters for name, param in params.items(): print(name, param) if param.annotation != param.empty: # 注入类属性 setattr(cls, name, TypeCheck(name, param.annotation)) def __call__(self, *args, **kwargs): return self.cls(*args, **kwargs) # 新建一个Person对象 @TypeInject class Person: # Person = TypeInject(Person) def __init__(self, name: str, age: int): self.name = name self.age = age p1 = Person('tom', 18) p2 = Person('tom', 20)

    运行结果

    name name:str age age:int set set set set 运行结果没有报错, 且可以看到执行顺序
    最新回复(0)