描述器协议
描述器协议包括以下3个方法:
- object.
__get__
(self, instance, owner)
调用时得到类的属性(类属性访问控制)或者类的实例的属性(实例属性访问控制),owner是属性所在类,instance是属性所在类的实例或None(如果通过类进行访问的话)。该方法要么返回属性值,要么抛出AttributeError异常。 - object.
__set__
(self, instance, value)
调用时给实例的属性赋予新值 - object.
__delete__
(self, instance)
调用时删除实例的属性
描述器是什么
那什么是描述器呢?一般来讲,描述器就是有“绑定行为”的对象属性,该属性的访问控制被描述器协议(__get__
(),__set__
(),__delete__
())重写。如果对象定义了任一个上述方法,那它就是描述器。
描述器分两种,一种是只定义了__get__
()方法的描述器叫非资料描述器(non-data descriptor),另一种是定义了__get__
()和__set__
()的描述器叫资料描述器(data descriptor)。
来个小例子吧
class DataDescriptor(object):
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
print "get method of %s" % self.val
return self.val
def __set__(self, instance, value):
print "set method of %s" % self.val
return self.val
class NonDataDescriptor(object):
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
print "get method of %s" % self.val
return self.val
class Context(object):
dd = DataDescriptor("data descriptor")
ndd = NonDataDescriptor("none data descriptor")
def __init__(self, val):
self.ndd = val
a = Context("haha~~")
a.dd
a.dd = "update"
a.dd
a.ndd
print(vars(Context))
print(vars(a))
print(a.ndd)
print(a.__dict__['ndd'])
print(type(a).__dict__['ndd'].__get__(a,Context))
#输出
get method of data descriptor
set method of data descriptor
get method of data descriptor
{'__module__': '__main__', 'dd': <__main__.DataDescriptor object at 0x10445a610>, 'ndd': <__main__.NonDataDescriptor object at 0x10445a650>, '__dict__': <attribute '__dict__' of 'Context' objects>, '__weakref__': <attribute '__weakref__' of 'Context' objects>, '__doc__': None, '__init__': <function __init__ at 0x104457938>}
{'ndd': 'haha~~'}
haha~~
haha~~
get method of none data descriptor
none data descriptor
描述器的调用
为说明描述器的调用,先看了解一下属性的查找过程,属性查找的过程也就是调用的过程。
实例属性的查找过程
对于a = A(), a.attr会先调用实例的__getattribute__
, 对描述器方法__get__
的调用就发生在__getattribute__
的内部,如果该方法抛出AttributeError异常,而且类A有定义__getattr__
方法,这时__getattr__
会被调用。如果将__getattribute__
内部查找顺序考虑在内,则属性的默认查找顺序是这样的:
- 如果attr在A或其基类的
__dict__
中, 且attr是资料描述器, 那么调用其__get__
方法, 否则 - 如果attr出现在a的
__dict__
中, 那么直接返回a.__dict__
['attr'], 否则 - 如果attr出现在A或其基类的
__dict__
中- 如果attr是非资料描述器,那么调用其
__get__
方法, 否则 - 返回
__dict__
['attr']
- 如果attr是非资料描述器,那么调用其
- 如果A有
__getattr__
方法,调用__getattr__
方法,否则 - 抛出AttributeError
类属性的查找过程
考虑到python中类也是对象,类是元类(metaclass)的实例,这样对应之后,类属性的查过程与实例属性的查找过程就基本相同了,除了第二步。由于类可能有父类,所以这里第二步就变成了:
只要A.__dict__
['attr']是一个descriptor,都调用其__get__
方法,否则返回A.__dict__
['attr']
这一步用python描述就是:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
正如上面说的,描述器是在__getattribute__
内部调用,所以重写__getattribute__
可以修改默认的描述器调用,甚至关闭描述器的调用。另外,描述器只在新式类(继承自object,python 2.2引入)可用。
应用
方法(method)
python面向对象的特征是建立在基于函数的环境之上的,非资料描述器就是两者之间的桥梁。函数中有__get__()
方法以方便在属性访问时绑定方法,也就是说所有的函数都是非资料描述器。类的字典中将方法存成函数,类定义的时候,与函数的声明一样,方法使用def或lambda来声明。
在python中,类似a.b()这样的方法调用是分两步的:获取属性a.b和调用。第一步是个参数绑定的过程,返回一个可调用的bound method对象,如果不能进行进行参数绑定则返回unbound method对象,不可调用。
class Method(object):
name = "class name"
def __init__(self,name):
self.name = name
def instance_method(self):
return self.name
@classmethod
def class_method(cls):
return cls.name
@staticmethod
def static_method(x,y):
return x + y
- 实例方法(instancemethod)
print(Method.__dict__['instance_method'])
a = Method("haha~")
print(a.instance_method)
print(type(a).__dict__['instance_method'].__get__(a,type(a)))
print(Method.instance_method)
print(Method.__dict__['instance_method'].__get__(None,Method))
print(a.instance_method())
print(Method.instance_method(a))
#输出
<function instance_method at 0x100a6a2a8>
<bound method Method.instance_method of <__main__.Method object at 0x100b5f590>>
<bound method Method.instance_method of <__main__.Method object at 0x100b5f590>>
<unbound method Method.instance_method>
<unbound method Method.instance_method>
haha~
haha~
实例方法调用时,需要有一个实例才能绑定,对于a.instance_method()这个实例就是函数定义时的self,对于Method.instance_method(a)这个实例就很明显了。
- 类方法
print(vars(a))
print(vars(Method))
print(a.class_method)
print(type(a).__dict__['class_method'].__get__(a,type(a)))
print(type(a).__dict__['class_method'].__get__(None,type(a)))
print(type(a).__dict__['class_method'].__get__(a,type(a))())
print(type(a).__dict__['class_method'].__get__(None,type(a))())
print(Method.class_method)
print(Method.__dict__['class_method'].__get__(None,Method))
print(Method.__dict__['class_method'].__get__(None,Method)())
#输出
{'name': 'haha~'}
{'__dict__': <attribute '__dict__' of 'Method' objects>, '__module__': '__main__', 'static_method': <staticmethod object at 0x10d864e88>, 'name': 'class name', 'instance_method': <function instance_method at 0x10d7832a8>, '__weakref__': <attribute '__weakref__' of 'Method' objects>, 'class_method': <classmethod object at 0x10d864e50>, '__doc__': None, '__init__': <function __init__ at 0x10d7830c8>}
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
class name
class name
<bound method type.class_method of <class '__main__.Method'>>
<bound method type.class_method of <class '__main__.Method'>>
class name
类方法绑定参数是类,实例并没有用。通过实例调用时,实例a中并没有class_method属性,按属性的查找过程,会到类里查找,并通过非资料描述器的__get__
方法调用,通过Method调用时,参考前面的类属性查找过程。
- 静态方法
print(a.static_method)
print(type(a).__dict__['static_method'])
print(type(a).__dict__['static_method'].__get__(a,type(a)))
print(type(a).__dict__['static_method'].__get__(None,type(a)))
#输出
<function static_method at 0x103645848>
<staticmethod object at 0x103634e18>
<function static_method at 0x103645848>
<function static_method at 0x103645848>
静态方法是一类特殊的方法,由定义可知,静态方法并不需要绑定到实例或类上,它只是一个普通的函数,调用时不再需要返回bound method对象。
待续
参考:
https://docs.python.org/3/reference/datamodel.html
https://www.cnblogs.com/xybaby/p/6270551.html
https://www.jianshu.com/p/250f0d305c35