前言
在Python开发中,我们很少能接触到描述器的直接使用,但对于熟练使用Python的开发者,了解Python描述器的工作原理,能让你更深入地了解Python以及其设计的优雅之处。
其实我们开发中遇到的很多例子,例如:
装饰器property、staticmethod、classmethod
function、bound method、unbound method
是不是都很熟悉,其实这些都与描述器有着千丝万缕的关系,这篇文章就为大家一一解答其中的奥秘。
什么是描述器?
一般来说,描述器是一个有「绑定行为」的对象属性,它的访问控制被描述器协议方法重写。
回忆一下,在编程中我们说「行为」一般指的是方法。
那么这就容易理解了,「绑定行为」的对象属性,就是指这个「对象属性」依托于了另外的对象,这个对象里包含了很多「方法」来控制这个行为。
描述器协议
对象属性依托的对象中,包含的「方法」不能随便定义,而是规定好的,实现这些方法,就是实现了描述器协议,具体有以下几个:
get
set
delete
只要实现以上方法其一,这个对象就叫做描述器。
定义了get和set的对象叫做资料描述器
只定义了__get___的对象叫做非资料描述器
这两者有什么区别呢?我们下面再做解释。
# 描述器的调用
我们来看一段描述器的代码:
class Age(object):
"""这个类实现了描述器协议"""
def __init__(self, value=20):
self.value = value
def __get__(self, obj, type=None):
print 'get --> obj: %s type: %s' % (obj, type)
return self.value
def __set__(self, obj, value):
print 'set --> obj: %s value: %s' % (obj, value)
self.value = value
class Person(object):
age = Age() # 这个属性通过描述器托管给了另一个类
def __init__(self, name):
self.name = name
person = Person('zhangsan')
print person.age
# get --> obj: <__main__.Person object at 0x105e815d0> type:
# 20
print Person.age
# get --> obj: None type:
# 20
person.age = 25
# set --> obj: <__main__.Person object at 0x105e815d0> value: 25
print person.age
# get --> obj: <__main__.Person object at 0x105e815d0> type:
# 25
我们从代码看出,Person类的类属性age被Age实现,Age类实现了set和get方法,对于Person类来说,age就是一个描述器。
我们通过输出结果看出,当调用age属性时,都调用了Age的get方法,但打印出的参数结果不同:
当调用方是实例时,obj是Person实例,type是type(Person)
当调用方是类时,obj是None,type是type(Person)
工作原理
这背后的机制到底是怎样的呢?
要解释这个现象,我们要先从属性被访问的机制来说,调用a.b会发生什么?
如果a的类是继承了object,也就是说这个类是新式类,那么a.b会调用getattribute方法,如果类中没有定义这个方法,那么默认会调用object的getattribute方法。
而object在getattribute中就默认调用了描述器,但调用细节取决于a是一个类还是一个实例:
如果a是一个实例,object的这个方法会把其变为:
type(a).__dict__['b'].__get__(a, type(a))
如果a是一个类,object的这个方法会把其变为:
a.__dict__['b'].__get__(None, a)
所以我们就能看到上面例子输出的结果。
也就是说,描述器的调用入口,取决于getattribute!
如果我们重写了getattribute,那么会阻止描述器的调用。
方法就是描述器
我们思考一个问题,当一个类中的实例变量名与一个方法同名时,例如类A有一个实例变量和方法都叫foo,那么A().foo会输出实例属性还是调用方法?
class A(object):
def __init__(self):
self.foo = 'abc'
def foo(self):
return 'xyz'
print A().foo # abc
我们看到,A().foo输出了abc,也就是实例属性的值,而不是调用这个方法,这是怎么回事?
我们执行如下代码:
print dir(A.foo)
# ['__call__', '__class__', '__get__', '__delattr__', '__doc__', ...
看到了吗?dir(A.foo)包含了get方法,我们在上面知道描述器的定义是:只要实现了get、set、del其一,这个对象就是描述器。
也就是说:方法就是一个描述器,而且是一个非资料描述器。
其实在调用一个属性时,具体的执行顺序是这样的:
__getattribute()__ -> 资料描述器 > 实例变量 > 非资料描述器 > __getattr()__
当一个类中的实例变量名与一个方法同名时:
如果描述器是资料描述器,优先使用资料描述器
如果描述器是非资料描述器,优先使用字典中的属性
由于每个方法都是一个非资料描述器,所以优先使用实例变量。
到这里我们可以总结一下:
getattribute只对新式类的实例有用
描述器的调用是因为object的getattribute
重写getattribute方法会阻止正常描述器的调用
方法都是非资料描述器
实例和类调用get结果不一样
资料描述器 > 实例变量 > 非资料描述器调用
function/unbound method/bound method
我们常见的function、unbound method、bound method有什么区别呢?
class A(object):
def foo(self):
return 'xyz'
print A.__dict__['foo'] #
print A.foo #
print A().foo # >
它们的区别如下:
function就是一个函数,因为其实现了get,因此每个函数都是一个非资料描述器
类的字典把方法当做函数存储
当方法被实例调用时,返回绑定的方法(bound method)
当方法被类调用时,返回非绑定的方法(unbound method)
property/staticmethod/classmethod
Python把一些使用特别普遍的功能打包成了独立的函数,例如property、staticmethod、classmethod,这些方法都是基于描述器协议实现的。
property的Python版实现:
class property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self.fget
if self.fget is None:
raise AttributeError(), "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
return self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
return self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
staticmethod的Python版实现:
class staticmethod(object):
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
return self.func
classmethod的Python版实现:
class classmethod(object):
def __init__(self, func):
self.func = func
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.func(klass, *args)
return newfunc
由此可见,通过描述符我们可以实现强大而灵活的属性和方法管理,但强大也意味着责任大,在合适的场景使用才能起到最佳的效果。
如果此文章能给您带来小小的工作效率提升,不妨小额赞助我一下,以鼓励我写出更好的文章!