描述器(又称描述符)(Descriptor)
描述器:如果一个类中实现了__get__, __set__, __delete__
三个方法中的任何一个,那么这样的类的实例就称为描述器。 当某一个类的类属性是一个描述器的时候,通过这个类或者类的实例来访问、修改或删除这个类属性时,就会分别触发描述器的__get__, __set__和__delete__
方法。
class Descriptor():
def __get__(self, instance, owner):
return {'instance': instance, 'owner': owner, 'value': 12345}
glb_descriptor = Descriptor() # 在模块全局声明一个描述器
class A:
prop = glb_descriptor
class B:
def __init__(self):
self.prop = glb_descriptor # 由于描述器不是B类的类属性,所以不会起作用。
class C:
cls_descriptor = Descriptor() # 在类定义中声明一个描述器
def __init__(self):
# 如下三种方式都能将描述器的__get__返回内容添加到实例的__dict__属性中,但内容略有不同,主要区别在于instance不同。
self.prop = self.cls_descriptor # c.prop: {'instance': <__main__.C object at 0x00000235799B6210>, 'owner': <class '__main__.C'>, 'value': 12345}
# self.prop = C.cls_descriptor # c.prop: {'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
# self.prop = type(self).cls_descriptor # c.prop: {'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
a = A()
b = B()
c = C()
print('a.prop:', a.prop)
print('b.prop:', b.prop)
print('c.prop:', c.prop)
print('-' * 25, '分隔线', '-' * 25)
print(A.prop)
print(C.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(a.__dict__) # a实例的__dict__没有内容
print(c.__dict__) # c实例的__dict__中存储了描述器__get__方法的返回值。因为在c的实例化过程中,`__init__`方法里就已经通过"实例.属性"(self.cls_descriptor)的方式触发了描述器的`__get__`方法。
输出结果:
a.prop: {'instance': <__main__.A object at 0x0000017589326190>, 'owner': <class '__main__.A'>, 'value': 12345}
b.prop: <__main__.Descriptor object at 0x00000175893256D0>
c.prop: {'instance': <__main__.C object at 0x0000017589326210>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.A'>, 'value': 12345}
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{}
{'prop': {'instance': <__main__.C object at 0x0000017589326210>, 'owner': <class '__main__.C'>, 'value': 12345}}
非数据描述器: 只实现了__get__
方法,未实现__set_
_和__delete_
_方法中的任何一个。这种情况下,实例优先访问自身的属性,如果不存在,再找描述器。
class Descriptor():
def __get__(self, instance, owner) :
return {'instance': instance, 'owner': owner, 'value': 12345}
class C:
cls_descriptor = Descriptor() # 在类定义中声明一个描述器
def __init__(self, prop):
self.cls_descriptor = prop # cls_descriptor是非数据描述器,prop值会保存在实例自己的同名属性中。
c = C('88888')
print('c.cls_descriptor:', c.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(C.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__) # 由于是非数据描述器,给实例中与类属性描述器同名的属性赋值不会触发描述器,而是直接保存在实例属性中。
输出结果:
c.cls_descriptor: 88888
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
88888
{'cls_descriptor': '88888'}
数据描述器: 描述器实现了__set__或__delete__方法,此时通过实例访问属性时,优先触发描述器,而不是优先访问实例属性。
class Descriptor():
def __get__(self, instance, owner) :
return {'instance': instance, 'owner': owner, 'value': 12345}
def __set__(self, instance, value):
instance.papapa = value
class C:
cls_descriptor = Descriptor() # 在类定义中声明一个描述器
def __init__(self, prop):
self.cls_descriptor = prop # 类属性cls_descriptor是描述器,通过实例访问这个属性会触发描述器的__set__方法,而不会在实例中存储一个同名属性。
c = C('88888')
print('c.cls_descriptor:', c.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(C.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__) # 由于cls_descriptor是数据描述器,所以实例在初始化时`self.cls_descriptor = prop`实际上会触发描述器的`__set__`方法,这样,实例自身的`__dict__`中并没有成功填加与描述器同名的属性。。
输出结果:
c.cls_descriptor: {'instance': <__main__.C object at 0x000001BBE5075B90>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': <__main__.C object at 0x000001BBE5075B90>, 'owner': <class '__main__.C'>, 'value': 12345}
{'papapa': '88888'}
下面为了说明属性查找的优先级,我直接通过实例的__dict__字典来添加实例属性,但在通过实例.属性的方式来查找属性时,仍然会显示调用描述器的结果。
属性查找顺序: 数据描述器 > 实例属性 > 非数据描述器
class Descriptor():
def __get__(self, instance, owner) :
return {'instance': instance, 'owner': owner, 'value': 12345}
def __set__(self, instance, value):
instance.papapa = value
class C:
cls_descriptor = Descriptor() # 在类定义中声明一个描述器
def __init__(self, prop):
self.cls_descriptor = prop # 类属性cls_descriptor是描述器,通过实例访问这个属性会触发描述器的__set__方法,而不会在实例中存储一个同名属性。
c = C('88888')
c.__dict__['cls_descriptor'] = '这是实例自己的属性' # 为了不触发描述器的__set__方法,直接通过实例的__dict__字典为其添加实例属性。
print('c.cls_descriptor:', c.cls_descriptor) # 这里仍然触发了描述器方法,而不是显示实例自身的属性的值
print('-' * 25, '分隔线', '-' * 25)
print(C.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__) # 这里保存了实例初始化时触发描述器时生成的属性,以及直接通过__dict__字典添加的实例属性
输出结果:
c.cls_descriptor: {'instance': <__main__.C object at 0x000002189035BAD0>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': <__main__.C object at 0x000002189035BAD0>, 'owner': <class '__main__.C'>, 'value': 12345}
{'papapa': '88888', 'cls_descriptor': '这是实例自己的属性'}
__getattribute__
魔术方法的等价实现: __getattribute__
方法是用c实现的,如非必需要,尽量不要覆写此方法。此方法的内部实现也体现了python内部属性访问机制的复杂性。 但其内部本身不包含对 __getattr__
方法的调用。即,只有当__getattribute__
返回AttributeError时, __getattr__
方法才会被触发。
如下为官方文档中使用python写的__getattribute__
的内部机制的模拟实现。
def find_name_in_mro(cls, name, default):
"Emulate _PyType_Lookup() in Objects/typeobject.c"
for base in cls.__mro__:
if name in vars(base):
return vars(base)[name]
return default
def object_getattribute(obj, name):
"Emulate PyObject_GenericGetAttr() in Objects/object.c"
null = object()
objtype = type(obj)
cls_var = find_name_in_mro(objtype, name, null)
descr_get = getattr(type(cls_var), '__get__', null)
if descr_get is not null:
if (hasattr(type(cls_var), '__set__')
or hasattr(type(cls_var), '__delete__')):
return descr_get(cls_var, obj, objtype) # data descriptor
if hasattr(obj, '__dict__') and name in vars(obj):
return vars(obj)[name] # instance variable
if descr_get is not null:
return descr_get(cls_var, obj, objtype) # non-data descriptor
if cls_var is not null:
return cls_var # class variable
raise AttributeError(name)
下面是我写的一个类似的等价实现,当然没有官方写的好。
def find_in_mro(cls_obj, name):
for cls in cls_obj.mro():
if name in vars(cls):
return cls, vars(cls)[name]
else:
return None, None
def object_getattribute(obj, name):
cls_obj = type(obj)
cls_prop, prop_obj = find_in_mro(cls_obj, name) # 查找类属性
if prop_obj: # 存在同名类属性
_, descriptior_get = find_in_mro(type(prop_obj), '__get__')
_, descriptior_set = find_in_mro(type(prop_obj), '__set__')
_, descriptior_del = find_in_mro(type(prop_obj), '__delete__')
if descriptior_get: # 类属性是描述器
if descriptior_set or descriptior_del: # 类属性是数据描述器
return descriptior_get(prop_obj, obj, cls_obj)
else: # 类属性是 非数据描述器
if name in vars(obj): # 存在同名实例属性
return vars(obj)[name]
else: # 不存在同名实例属性
return descriptior_get(prop_obj, obj, cls_obj)
else:
if name in vars(obj): # 存在同名实例属性
return vars(obj)[name]
else:
return prop_obj
else: # 不存在同名类属性
if name in vars(obj): # 存在同名实例属性
return vars(obj)[name]
else: # 类和实例中都未找到该属性
raise AttributeError(name)
下面是对自己写的方法的一个测试:
class Descriptor:
def __get__(self, instance, owner):
print('描述器 __get__ 被调用')
return '描述器get返回值'
def __set__(self, instance, value):
print('描述器 __set__ 被调用')
pass # 让其成为 数据描述器
class A:
cls_prop = Descriptor()
pass
class B(A):
b_cls_prop = 'value of b_cls_prop'
pass
输出结果: 如果注释掉描述器的__set__
方法,结果将会不同。
描述器 __get__ 被调用
描述器get返回值
{}
------------------------- 分隔线 -------------------------
描述器 __set__ 被调用
描述器 __get__ 被调用
描述器get返回值
{}
------------------------- 分隔线 -------------------------
描述器 __get__ 被调用
描述器get返回值
value of b_cls_prop
标签:__,instance,obj,descriptor,prop,Descriptor,描述,cls
From: https://www.cnblogs.com/rolandhe/p/18588769