描述器
魔术方法:__get__()
、__set__()
、 __delete__()
、__set_name__()
Python中的描述器
描述器
描述器涉及的魔术方法
用到3个魔术方法: __get__()
、__set__()
、 __delete__()
,具体方法签名如下:
object.__get__(self, instance, owner)
object.__set__(self, instance, value)
object.__delete__(self, instance)
- self 指代当前实例,调用者
- instance 是owner的实例(属主的实例)
- owner 是属性的所属的类(属主)
具体含义可参考下面的演示示例
引子
# 例1:
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
class B:
x = A()
def __init__(self):
print('init class B ~~~')
print(B.x.a)
print('~' * 20)
b = B()
print(b.x.a)
--------输出结果-------
init class A ~~~
100
~~~~~~~~~~~~~~~~~~~~
init class B ~~~
100
并没有什么特殊的,也很容易理解。但是如果加上在A类中实现描述器涉及的魔术方法呢?
# 例2:
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
def __get__(self, instance, owner):
print('in get func ~~~', self, instance, owner) # self即为类A的实例,owner为属主,即类B,instance为owner(类B)的实例
return self # get方法需要给定一个返回值,这里为了方便B/b能够访问A实例的其他属性,一般返回A是实例,即self
class B:
x = A() # B类属性x为A的实例,且A类实现了__get__方法,访问类B或B类实例的属性x,则会去调用A类的__get__方法
def __init__(self):
print('init class B ~~~')
print(B.__dict__) # x为类B的属性
print(B.x.a) # 使用类来访问
print('-' * 30)
b = B()
print(b.__dict__) # 为空,在实例字典中找不到,从实例的类字典找
print(b.x.a) # 使用类实例来访问
---------------输出结果-------------------
init class A ~~~
{'__module__': '__main__', 'x': <__main__.A object at 0x1059e3438>, '__init__': <function B.__init__ at 0x105abd378>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
in get func ~~~ <__main__.A object at 0x1059e3438> None <class '__main__.B'>
100
------------------------------
init class B ~~~
{}
in get func ~~~ <__main__.A object at 0x1059e3438> <__main__.B object at 0x1059e35c0> <class '__main__.B'>
100
- 类A在这里就是一个描述器(非数据描述器),实现了
__get__
方法- 如果类属性是类实例(即为描述器的实例),这个类被成为owner
# 例3:类实例的属性为描述器的类实例
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
def __get__(self, instance, owner):
print('in get func ~~~', self, instance, owner)
return self
class B:
x = A()
def __init__(self):
print('init class B ~~~')
self.x = A() # x为类实例的属性
b = B()
print(b.__dict__) # 在实例的字典中找x,能找到,则不会从类的字典中找
print(b.x.a) # 并没有进入类A的get函数,也就是说,如果是类实例的属性,没有该表现
---------------输出结果------------------
init class A ~~~
init class B ~~~
init class A ~~~
{'x': <__main__.A object at 0x10a83b588>}
100
- 只有类属性是类实例时,通过类或者类实例访问该类属性才会转而调用描述器的
__get__
方法,如果为类实例的属性是描述器实例,没有该表现
描述器定义
Python中,一个类实现了 __get__
、 __set__
、 __delete__
三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议
- 仅实现了
__get__
,就是非数据描述符(non-data descriptor) - 实现了
__get__
、__set__
就是数据描述符(data descriptor) - 如果一个类的类属性设置为描述器实例,那么它被称为owner属主
- 当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法
接下来,我们来进一步看看__set__
方法在描述器中的作用
例4:数据描述器
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
def __get__(self, instance, owner):
print('in get func ~~~', self, instance, owner)
return self
def __set__(self, instance, value):
print('in set func ~~~~', self, instance, value)
self.data = value
# instance.x = value # instance.x set值时会再次转而调用set,因此会RecursionError: maximum recursion depth exceeded
instance.__dict__['x'] = value # 使用字典的方式避免递归调用
class B:
x = A()
def __init__(self):
print('init class B ~~~')
# 为实例增加属性x,且刚好为类属性的同名属性(x),且该类属性又是数据描述器实例(即实现了set),则会转而调用set方法
self.x = 'b.x' # 如果为非数据描述器,则仅仅是类B的实例的属性而已,参考例3
print(B.x)
print(B.x.a)
print('-' * 30)
b = B()
print(b.__dict__)
print(b.x) # 尽管b的字典中此时有x属性,但是描述器的优先级更高,转而调用get,返回A类实例对象
print('-' * 30)
print(b.x.data)
print('-' * 30)
print(b.x.a)
print('~' * 30)
b.x = 100
print(b.__dict__)
print(b.x)
print('~'*30)
B.x = 200 # 此时B类属性被替换,B的类属性不再是描述器实例
print(b.__dict__)
print(b.x)
-------------输出结果-----------------------
init class A ~~~
in get func ~~~ <__main__.A object at 0x100512b70> None <class '__main__.B'>
<__main__.A object at 0x100512b70>
in get func ~~~ <__main__.A object at 0x100512b70> None <class '__main__.B'>
100
------------------------------
init class B ~~~
in set func ~~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> b.x
{'x': 'b.x'}
in get func ~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> <class '__main__.B'>
<__main__.A object at 0x100512b70>
------------------------------
in get func ~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> <class '__main__.B'>
b.x
------------------------------
in get func ~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> <class '__main__.B'>
100
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
in set func ~~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> 100
{'x': 100}
in get func ~~~ <__main__.A object at 0x100512b70> <__main__.B object at 0x100578588> <class '__main__.B'>
<__main__.A object at 0x100512b70>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{'x': 100}
100
- 如果类B实例的属性x的同名类属性x是数据描述器实例,则类B或者类实例b访问属性,则遵循描述器协议访问
- 数据描述器 优先于 实例的
__dict__
- 实例的
__dict__
优先于 非数据描述器
- py3.6新增
__set_name__
方法,通过这个方法,就是可以知道属主类和属主类的类属性名 __set_name__
方法会在属主类构建的时候调用
# 例:__set_name__()
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
def __get__(self, instance, owner):
print('in get func ~~~', self, instance, owner)
return self
def __set__(self, instance, value):
print('in set func ~~~~', self, instance, value)
self.data = value
instance.__dict__['x'] = value
def __set_name__(self, owner, name):
print('in set_name func ~~~', owner, name)
self.name = name
class B:
x = A() # 类属性创建时调用描述器的__set_name__方法
def __init__(self):
print('init class B ~~~')
self.x = 'b.x'
print('-' * 30)
print(B.x)
print(B.x.name)
print('-' * 30)
b = B()
print(b.x)
print(b.x.name)
--------------输出结果-------------
init class A ~~~
in set_name func ~~~ <class '__main__.B'> x
------------------------------
in get func ~~~ <__main__.A object at 0x104335588> None <class '__main__.B'>
<__main__.A object at 0x104335588>
in get func ~~~ <__main__.A object at 0x104335588> None <class '__main__.B'>
x
------------------------------
init class B ~~~
in set func ~~~~ <__main__.A object at 0x104335588> <__main__.B object at 0x104407f28> b.x
in get func ~~~ <__main__.A object at 0x104335588> <__main__.B object at 0x104407f28> <class '__main__.B'>
<__main__.A object at 0x104335588>
in get func ~~~ <__main__.A object at 0x104335588> <__main__.B object at 0x104407f28> <class '__main__.B'>
x
Python中的描述器
描述器在Python中应用非常广泛
- Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为
- property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为
# 例1:python中的数据描述器,@property
class A:
def __init__(self):
self.__x = 100
@property
def x(self):
return self.__x
a = A()
print(a.x)
a.x = 100
-------输出结果---------
100
Traceback (most recent call last):
File "/Users/tina/PycharmProjects/airtest_test/t2.py", line 13, in <module>
a.x = 100
AttributeError: can't set attribute
怎么理解property 装饰器实现了数据描述器呢?看如下例子
# 例2:
class A:
def __init__(self):
self.a = 100
print('init class A ~~~')
def __get__(self, instance, owner):
print('in get func ~~~', self, instance, owner)
return self
def __set__(self, instance, value):
print('in set func ~~~~', self, instance, value)
if instance:
raise AttributeError("can't set attribute")
class B:
x = A()
def __init__(self):
print('init class B ~~~')
b = B()
print(b.x)
b.x = 100 # AttributeError: can't set attribute
------------输出结果---------------
Traceback (most recent call last):
File "/Users/tina/PycharmProjects/airtest_test/t2.py", line 25, in <module>
b.x = 100
File "/Users/tina/PycharmProjects/airtest_test/t2.py", line 14, in __set__
raise AttributeError("can't set attribute")
AttributeError: can't set attribute
...
...
由此可见,通过数据描述器,可以控制修改实例属性,对比参考例1
- 描述器的其他应用场景:如对实例的数据进行校验(类型校验等)
参考
- magedu