描述器的表现
-
用到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
- 运行结果没有报错, 且可以看到执行顺序