描述器 Descriptors
描述器表现
- 用到的魔术方法:
__get__()
、__set__()
、__delete()__
- 方法签名如下
object.__get__(self, instance, owner)
object.__set__(self, instance, owner)
object.__delete__(self, instance)
- self值当前实例,调用者
- instance是owner的实例
- owner是属性的所属的类
__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('-' * 20)
print(B.x)
# print(B.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'
print('=' * 20)
b = B()
print(b.x)
# print(b.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'
# 运行结果
A.init
--------------------
A.__get__<__main__.A object at 0x000001A4C0198630>None<class '__main__.B'>
None
====================
B.init
A.__get__<__main__.A object at 0x000001A4C0198630><__main__.B object at 0x000001A4C0198668><class '__main__.B'>
None
因为定义了__get__
方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对A的实例的访问,就会调用__get__
方法
self、instance、owner`三个参数的意思
__get__(self, instance, owner)方法的签名,会传入3个参数
B.x调用返回<__main__.A object at 0x000001A4C0198630>None<class '__main__.B'>
b.x调用返回<__main__.A object at 0x000001A4C0198630><__main__.B object at 0x000001A4C0198668><class '__main__.B'>
self
对应都是A的实例owner
对应都是B类instance
说明
None
表示不是B类的实例
<__main__.B object at 0x000001A4C0198668>
表示是B的实例,对应调用B().x
使用返回值解决。返回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 # 解决返回None的问题
class B:
x = A()
def __init__(self):
print('B.init')
self.b = A() # 实行属性也指向一个A的实例
print('-' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
print(b.b) # 并没有触发__get__
# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000024595758668>None<class '__main__.B'>
<__main__.A object at 0x0000024595758668>
A.__get__<__main__.A object at 0x0000024595758668>None<class '__main__.B'>
a1
====================
B.init
A.init
A.__get__<__main__.A object at 0x0000024595758668><__main__.B object at 0x00000245957586A0><class '__main__.B'>
<__main__.A object at 0x0000024595758668>
A.__get__<__main__.A object at 0x0000024595758668><__main__.B object at 0x00000245957586A0><class '__main__.B'>
a1
<__main__.A object at 0x00000245957586D8>
从运行结果可以看出,只要类属性是类的实例才行
描述器定义
Python中,一个类实现了__get__
、__set__
、__delete__
三个方法中的任何一个方法,就是描述器。实现三个中的某些方法,就支持了描述器协议
- 仅实现
__get__
,就是非数据描述符 non-data descriptor - 实现了
__get__
和剩下两个任意一种,就是数据描述符 data descriptor
如果一个类的类属性设置为描述器实例,name它被称为owner属主
当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法
属性的访问顺序
为上例中的类B增加实例属性x
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 # 解决返回None的问题
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
print('-' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
# print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'
# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000021C903C86A0>None<class '__main__.B'>
<__main__.A object at 0x0000021C903C86A0>
A.__get__<__main__.A object at 0x0000021C903C86A0>None<class '__main__.B'>
a1
====================
B.init
b.x
类A只实现了__get__()
方法,b.x访问到了实例的属性,而不是描述器
__set__
为类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 # 解决返回None的问题
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('-' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
print(b.x.data)
# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000021C2C9486A0>None<class '__main__.B'>
<__main__.A object at 0x0000021C2C9486A0>
A.__get__<__main__.A object at 0x0000021C2C9486A0>None<class '__main__.B'>
a1
====================
B.init
A.__set__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8>b.x
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
<__main__.A object at 0x0000021C2C9486A0>
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
a1
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
b.x
所有的b.x
就会访问描述器的__get__()
方法,代码中返回的self
就是描述器实例,它的实例字典中就保存着a1
和data
属性,可以打印b.x.__dict__
看这些属性
属性查找顺序
实例的__dict__
优先于非数据描述器
数据描述器优先于实例的__dict__
__delete__
方法有同样的效果,有了这个方法,也就是数据描述器
Python中的描述器
- 描述器在Python中应用非常广泛
- Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为
property()
函数实现为一个数据描述器。因此,实例不能覆盖属性和行为
class A:
@classmethod
def foo(cls): # 非数据描述器
pass
@staticmethod
def bar(): # 非数据描述器
pass
@property
def z(self): # 数据描述器
return 5
def getfoo(self): # 非数据描述器
return self.foo
def __init__(self): # 非数据描述器
self.foo = 100
self.bar = 200
# self.z = 300
a = A()
print(a.__dict__) # {'foo': 100, 'bar': 200}
print(A.__dict__)
foo、bar都可以在事例中覆盖,但是z不可以
新增方法
3.6新增描述器方法__set_name__
,它在属主类构建的时候就会调用
class A:
def __init__(self):
print('A init')
def __get__(self, instance, owner):
print(1, self, instance, owner)
return self
def __set_name__(self, owner, name):
print(2, self, owner, name)
self.name = name
class B:
x = A() # 类属性创建时调用描述器的__set_name__方法
print('-' * 30)
print(B().x)
# 执行结果
A init
2 <__main__.A object at 0x0000024F08AB47B8> <class '__main__.B'> x
------------------------------
1 <__main__.A object at 0x0000024F08AB47B8> <__main__.B object at 0x0000024F08AB8400> <class '__main__.B'>
<__main__.A object at 0x0000024F08AB47B8>
提供这个方法,就是可以知道属主类和属主类的类属性名
练习
实现StaticMethod装饰器
- 实现StaticMethod装饰器,完成staticmethod装饰器的功能
class StaticMethod:
def __init__(self, fn):
self._fn = fn
def __get__(self, instance, owner):
return self._fn
class A:
@StaticMethod
# stmtd = StaticMethod(stmtd)
def stmtd():
print('static method')
A.stmtd()
A().stmtd()
# 执行结果
static method
static method
实现ClassMethod装饰器
- 实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial
# 类classmethod装饰器
class ClassMethod:
def __init__(self, fn):
self._fn = fn
def __get__(self, instance, cls):
ret = partial(self._fn, cls)
return ret
class A:
@ClassMethod
# clsmtd = ClassMethod(clsmtd)
# 调用a.clsmtd() 或者 A().clsmtd()
def clsmtd(cls):
print(cls.__name__)
print(A.__dict__)
print(A.clsmtd)
A.clsmtd()
A().clsmtd()
对实例的数据进行校验
class Person:
def __init__(self, name:str, age:int):
self.name = name
self.age = age
对上面的类的实例的属性name、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(value)
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:
# 类属性,由装饰器注入
def __init__(self, name:str, age:int):
self.name = name
self.age = age
print(Person.__dict__)
p1 = Person('tom', 20)
p2 = Person('jerry', '18')
- 因为p2的age传参没有按照int类型传参,所以会抛出异常
TypeError: 18
将上例函数装饰器改为类装饰器
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(value)
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 # Person = TypeInject(Person)
class Person:
# 类属性,由装饰器注入
def __init__(self, name:str, age:int):
self.name = name
self.age = age
print(Person.__dict__)
p1 = Person('tom', 20)
p2 = Person('tom', 19)