Python描述符详解
一,什么是描述符?
- 参考:https://www.cnblogs.com/Jimmy1988/p/6808237.html
- 官方定义:python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法由
__get__()
、__set__()
和__delete()
。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。
二,__dict__
属性
-
作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为
{attr_key : attr_value}
-
对象属性的访问顺序:
- 实例属性
- 类属性
- 父类属性
__getattr__()
方法
class Test:
cls_val = 1
def __init__(self):
self.ins_val = 10
t = Test()
print(Test.__dict__)
"""
{'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
'__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None}
"""
print(t.__dict__)
"""
{'ins_val': 10}
"""
# 更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val
t.cls_val = 20
print(t.__dict__)
"""
{'ins_val': 10, 'cls_val': 20}
"""
print(Test.__dict__)
"""
{'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
'__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None}
"""
# 更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水)
Test.cls_val = 30
print(t.__dict__)
"""
{'ins_val': 10, 'cls_val': 20}
"""
print(Test.__dict__)
"""
{'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
'__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None}
"""
从以上代码可以看出,实例t的属性并不包含cls_val
,cls_val
是属于类Test的。
三,魔法方法:__get__(), __set__(), __delete__()
- 方法的原型为:
__get__(self, instance, owner)
__set__(self, instance, value)
__del__(self, instance)
class Desc:
def __get__(self, instance, owner):
print("__get__...")
print("self : \t\t", self)
print("instance : \t", instance)
print("owner : \t", owner)
print('=' * 40, "\n")
def __set__(self, instance, value):
print('__set__...')
print("self : \t\t", self)
print("instance : \t", instance)
print("value : \t", value)
print('=' * 40, "\n")
class TestDesc:
x = Desc()
t = TestDesc()
print(t.x)
"""
__get__...
self : <__main__.Desc object at 0x000001F28742C8D0>
instance : <__main__.TestDesc object at 0x000001F287458E10>
owner : <class '__main__.TestDesc'>
========================================
"""
可以看到,实例化类TestDesc
后,调用对象t访问其属性x,会自动调用类Desc
的 __get__
方法,由输出信息可以看出:
self
:Desc
的实例对象,其实就是TestDesc
的属性xinstance
:TestDesc
的实例对象,其实就是towner
: 即谁拥有这些东西,当然是TestDesc
这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的,是instance所属的类
到此,我可以揭开小小的谜底了,其实,Desc
类就是是一个描述符(描述符是一个类哦),为啥呢?因为类Desc
定义了方法 __get__
, __set__
.
所以,某个类,只要是内部定义了方法 __get__
, __set__
, __delete__
中的一个或多个,就可以称为描述符
问题1. 为什么访问 t.x
的时候,会直接去调用描述符的 __get__()
方法呢?
-
访问
Owner
的__getattribute__()
方法(其实就是TestDesc.__getattribute__()
),首先访问实例属性,发现没有,然后去访问类TestDesc
的属性,找到了! -
判断属性 x 为一个描述符时,它就会做一些变动了,将
TestDesc.x
转化为TestDesc.__dict__['x'].__get__(t, TestDesc)
来访问 -
进入类
Desc
的__get__()
方法,进行相应的操作
问题2. 描述符的对象 x 其实是类 TestDesc
的类属性,那么可不可以把它变成实例属性呢?
class Desc(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
print("__get__...")
print('name = ', self.name)
print('=' * 40, "\n")
def __set__(self, instance, value):
print("__set__...")
class TestDesc(object):
x = Desc('x')
def __init__(self):
self.x = Desc('x')
self.y = Desc('y')
def __getattribute__(self, item):
print(item,'1111')
return super(TestDesc, self).__getattribute__(item)
t = TestDesc()
print(t.x)
print(t.y)
print(t.__dict__)
"""
__set__...
__get__...
name = x
========================================
<__main__.Desc object at 0x000001A44CA58E10>
{'y': <__main__.Desc object at 0x000001A44CA58E10>}
"""
"""
为什么没有打印t.y的信息呢?
因为没有访问__get__() 方法啊。
因为实例化t的时候,并没有y属性。而是在__init__方法中创建的实例属性。
所以在第一步查找实例属性的时候就找到了y属性。
而x属性是TestDesc的类属性,在t实例化之前就保存在TestDesc的类属性中。
在__init__方法中给实例x属性赋值时,发现TestDesc的类属性中有x,而且x是一个描述符。
那么会触发x的__set__方法。而不会创建实例属性。(如果没有__set__方法,则会成功创建实例属性。)
"""
问题3. 如果 类属性的描述符对象 和 实例属性描述符的对象 同名时,咋整?
class Desc(object):
def __init__(self, name):
self.name = name
print("__init__(): name = ", self.name)
def __get__(self, instance, owner):
print("__get__() ...")
return self.name
def __set__(self, instance, value):
print("__set__() ...")
self.value = value
class TestDesc(object):
_x = Desc('x')
def __init__(self, x):
self._x = x
# 以下为测试代码
t = TestDesc(10)
print(t._x)
"""
__init__(): name = x
__set__() ...
__get__() ...
x
"""
print(t.__dict__) # {}
print(TestDesc.__dict__)
"""
{'__module__': '__main__',
'_x': <__main__.Desc object at 0x000001D3BD1DC898>,
'__init__': <function TestDesc.__init__ at 0x000001D3BD26A598>,
'__dict__': <attribute '__dict__' of 'TestDesc' objects>,
'__weakref__': <attribute '__weakref__' of 'TestDesc' objects>,
'__doc__': None}
"""
"""
不对啊,按照惯例,t._x 会去调用 __getattribute__() 方法,然后找到了 实例t 的 _x 属性就结束了,
为啥还去调用了描述符的 __get__() 方法呢?
这是因为,_x属性是TestDesc的类属性,在t实例化之前就保存在TestDesc的类属性中。
在__init__方法中给实例_x属性赋值时,发现TestDesc的类属性中有_x,而且_x是一个描述符。
那么会触发x的__set__方法。而不会创建实例属性。(如果没有__set__方法,则会成功创建实例属性。)
"""
如果删除__set__()
方法会发生什么?
class Desc(object):
def __init__(self, name):
self.name = name
print("__init__(): name = ", self.name)
def __get__(self, instance, owner):
print("__get__() ...")
return self.name
class TestDesc(object):
_x = Desc('x')
def __init__(self, x):
self._x = x
t = TestDesc(10)
print(t._x)
"""
__init__(): name = x
10
"""
print(t.__dict__) # {'_x': 10}
print(TestDesc.__dict__)
"""
{'__module__': '__main__', '_x': <__main__.Desc object at 0x000001E4EA63C8D0>,
'__init__': <function TestDesc.__init__ at 0x000001E4EA6396A8>,
'__dict__': <attribute '__dict__' of 'TestDesc' objects>,
'__weakref__': <attribute '__weakref__' of 'TestDesc' objects>,
'__doc__': None}
"""
"""
这次没有调用__get__()方法。这是因为在TestDesc的__init__中创建实例属性时,发现_x是描述符。
但是_x是非数据描述符(只有__get__),依然创建了实例属性。
"""
问题4. 什么是数据描述符,什么是非数据描述符?
- 一个类,如果只定义了
__get__()
方法,而没有定义__set__(), __delete__()
方法,则认为是非数据描述符; 反之,则成为数据描述符
四,总结
- 实例化t时,如果在
__init__
中尝试创建和类属性中的描述符同名的属性;如果是数据描述符,则触发描述符的__set__
方法,不创建实例属性;如果是非数据描述符,则正常创建实例属性。 - 查找属性的顺序:
__getattribute__()
, 无条件调用- 实例属性(实例的
__dict__
) - 类属性(类的
__dict__
) - 如果在类属性中发现要查找的属性,尝试调用这个属性的
__get__
方法。没有就返回这个属性。 - 父类的属性
__getattr__()
方法