属性(attribute)是什么
an attribute is a way to get from one object to another
属性就是另一个对象的引用,python中一切都是对象。函数、类、类的实例等等,都是对象。
python描述器(descriptor)是什么
在Descriptor HowTo Guide中这样定义descriptor
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol.
也就是说,描述器其实是python对象中特殊的属性,这个属性的访问控制可以被重写。具体来说,当你获取(__get__
),设置(__set__
)和删除(__delete__
)一个属性时,如果这个属性是描述器,那么就会调用被重写的函数,而不是默认的函数。
descriptor协议
Python通过descriptor协议来判断一个属性是否是描述器。如果一个属性定义了__get__
, __set__
或者__delete__
方法中的任意一个,那么这个属性就是描述器,否则就是普通的属性。这三个方法也定义了这个属性被获取、设置或者删除时的行为。
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
调用过程
descriptor协议的具体工作过程是这样。
首先所有属性的调用都是通过__getattribute__()
这个方法,descirptor的__get__()
是在这个方法内被调用。
对象和类的调用过程有所不同,对象使用object.__getattribute__()
,类则是使用type.getattribute__()
,如果你对元类(metaclass)熟悉的话,应该能理解object是所有对象的type,type则是所有类的type。
对于类来说__getattribute__()
大致是这样工作的
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = type.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
对于一般对象来说,则会返回v.get__(self,type(self))
描述器实例
现在我们可以定义自己的描述器了。
class Desc(object):
def __init__(self):
self.val = 0
def __get__(self,obj,cls = None):
print("call __get__")
return self.val
def __set__(self,obj,val):
print("call __set__")
self.val = val
class MyClass(object):
x = Desc()
m = MyClass()
print(m.x)
m.x = 2
print(m.x)
输出是
call __get__
0
call __set__
call __get__
2
这里x是m的一个属性,x有__get__
和__set__
方法,因此x是描述器。
两类描述器
描述器分为data descriptor和non-data descriptor。
定义了__get__
和__set__
的是data descriptor,只定义了__get__
没有__set__
的是non-data descriptor。
non-data descriptor没有__set__
,因此在设置属性的时候会覆盖描述器,而不是调用描述器的__set__
方法。
之前已经有data descriptor的例子,下面是non-data descriptor的例子。
class Desc(object):
def __init__(self):
self.val = 0
def __get__(self,obj,cls = None):
print("call __get__")
return self.val
class MyClass(object):
x = Desc()
m = MyClass()
print(m.x)
m.x = 2
print(m.x)
输出是
call __get__
0
2
可以看到在m.x=2之后,m.x已经不是一个描述器,而是一个int对象了。
data descriptor可以完全控制(读和写)一个属性。在实例里面不能覆盖描述器,但仍然可以在类里面覆盖描述器。
non-data descriptor则只能读一个属性。对属性的任何修改都会覆盖描述器。
Python内置的描述器
描述器是Python中非常重要的一个特性,它用来方便地实现Python中的很多功能。
Python中内置的描述器包括 functions, properties, static methods, and class methods
函数作为描述器
函数是non-data descriptor
函数类大致是这样定义的
class Function(object):
...
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
所有函数都有__get__
方法,因此是描述器。
>>> def f(self):
pass
>>> hasattr(f,'__get__')
True
>>> hasattr(f,'__set__')
False
>>> hasattr(f,'__delete__')
False
接下来我们可以稍微考虑一下function和method的区别了
def myfunc(self):
print("ok")
class A(object):
f = myfunc
a = A()
print(myfunc)
print(A.__dict__['f'])
print(A.f)
print(A.__dict__['f'].__get__(None,A))
print(a.f)
print(myfunc.__get__(a,type(a)))
A.f(a)
a.f()
<function myfunc at 0x000000000061CB70>
<function myfunc at 0x000000000061CB70>
<function myfunc at 0x000000000061CB70>
<function myfunc at 0x000000000061CB70>
<bound method A.myfunc of <__main__.A object at 0x000000000235C1D0>>
<bound method A.myfunc of <__main__.A object at 0x000000000235C1D0>>
ok
ok
稍微解释一下上面的输出。
首先myfunc
和A.__dict__['f']
是完全一样的,都是一个函数对象的引用。
A.f
和A.__dict__['f'].__get__(None,A)
代表的是一样的东西,后者是前者内部的实现过程。因为函数是一个描述器,所以获取A.f会调用f的__get__
方法。正巧__get__
方法返回的是f自身。也就是说,myfunc和A.f未必一样。
最后a.f
和myfunc.__get__(a,type(a))
是一样的。获取a.f会调用f的__get__
方法,调用方式是__get__(a,type(a))
,返回的结果是一个bound method,这是一个绑定了实例a的函数,a会作为默认参数传给函数的以一个参数。其中__get__
方法对函数进行了绑定。
如果你想了解更多的细节,参考下面的资料:
Descriptor HowTo Guide
Python Attributes and Methods
Python Types and Objects