翻译自https://docs.python.org/2/howto/descriptor.htmlhtml
定义描述器, 总结描述器协议,并展现描述器是怎么被调用的。展现一个自定义的描述器和包括函数,属性(property), 静态方法(static method), 类方法在内的几个Python内置描述器。经过给出一个纯Python的实现和示例应用来展现每一个描述器是怎么工做的。python
学习描述器不只让你接触到更多的工具,还可让你更深刻地了解Python,让你体会到Python设计的优雅之处。程序员
通常来讲,一个描述器是一个有“绑定行为”的对象属性(object attribute),它的访问控制被描述器协议方法重写。这些方法是 __get__()
, __set__()
, 和 __delete__()
。有这些方法的对象叫作描述器。编程
默认对属性的访问控制是从对象的字典里面(__dict__)中获取(get), 设置(set)和删除(delete)它。举例来讲, a.x
的查找顺序是, a.__dict__['x']
, 而后 type(a).__dict__['x']
, 而后找 type(a)
的父类(按照mro里的顺序,不包括元类(metaclass)).若是查找到的值是一个描述器, Python就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪一个描述器方法。注意, 只有在新式类中时描述器才会起做用。(新式类是继承自 type
或者 object
的类)api
描述器是强大的,应用普遍的。描述器正是属性, 实例方法, 静态方法, 类方法和 super
的背后的实现机制。描述器在Python自身中普遍使用,以实现Python 2.2中引入的新式类。描述器简化了底层的C代码,并为Python的平常编程提供了一套灵活的新工具。ide
descr.__get__(self, obj, type=None) --> value
svn
descr.__set__(self, obj, value) --> None
函数
descr.__delete__(self, obj) --> None
工具
这是全部描述器方法。一个对象具备其中任一个方法就会成为描述器,从而在被看成对象属性时重写默认的查找行为。oop
若是一个对象同时定义了 __get__()
和 __set__()
,它叫作数据描述器(data descriptor)。仅定义了 __get__()
的描述器叫非数据描述器(经常使用于方法,固然其余用途也是能够的)
数据描述器和非数据描述器的区别在于:相对于实例的字典的优先级。若是实例字典中有与描述器同名的属性,若是描述器是数据描述器,优先使用数据描述器,若是是非数据描述器,优先使用字典中的属性。
下面一个描述器
class descr(object): def __init__(self, name='jack'): self._name = name def __get__(self, instance, owner): print('__get__...attr:%s instance:%s owner:%s' % (self._name, instance, owner)) return self._name def __set__(self, instance, value): print('__set__...attr:%s instance:%s value:%s' % (self._name, instance, value)) self._name = value class A(object): a = descr() def f(self): print('Im A.f') >>> a = A() >>> a.a __get__...attr:jack instance:<__main__.A object at 0x109224950> owner:<class '__main__.A'> 'jack' >>> a.a='my jack' __set__...attr:jack instance:<__main__.A object at 0x109224950> value:my jack >>> a.a __get__...attr:my jack instance:<__main__.A object at 0x109224950> owner:<class '__main__.A'> 'my jack'
假如f里也有同名属性a会是什么状况
class descr(object): def __init__(self, name='jack'): self._name = name def __get__(self, instance, owner): print('__get__...attr:%s instance:%s owner:%s' % (self._name, instance, owner)) return self._name def __set__(self, instance, value): print('__set__...attr:%s instance:%s value:%s' % (self._name, instance, value)) self._name = value class A(object): a = descr() def f(self): self.a = 'siri' print('Im A.f') >>> a = A() >>> a.f() __set__...attr:jack instance:<__main__.A object at 0x1079a2490> value:siri Im A.f >>> a.a __get__...attr:siri instance:<__main__.A object at 0x1079a2490> owner:<class '__main__.A'> siri
会发现虽然实例属性里虽然没定义描述器,但仍是走了咱们定义的描述器,这就是上面这句“若是实例字典中有与描述器同名的属性,若是描述器是数据描述器,优先使用数据描述器”
假如非数据描述器呢,只保留__get__
class descr(object): def __init__(self, name='jack'): self._name = name def __get__(self, instance, owner): print('__get__...attr:%s instance:%s owner:%s' % (self._name, instance, owner)) return self._name # def __set__(self, instance, value): # print('__set__...attr:%s instance:%s value:%s' % (self._name, instance, value)) # self._name = value class A(object): a = descr() def f(self): self.a = 'siri' print('Im A.f') >>> a = A() >>> a.f() Im A.f >>> a.a siri
可见,以上并无走咱们定义的描述器,即上面这句“若是是非数据描述器,优先使用字典中的属性”
假如描述符对象定义为对象属性会怎么样?
class A(object): a = descr() def f(self): self.b = descr('siri') print('Im A.f') >>> a = A() >>> a.f() Im A.f >>> a.a __get__...attr:jack instance:<__main__.A object at 0x103842490> owner:<class '__main__.A'> jack >>> a.b <__main__.descr object at 0x1038424d0>
可见返回了描述器对象的实例,这是因为object.__getattribute__会将a.b转换为type(a).__dict__[‘b’].__get__(a,type(a)),
而A类中没有“b”属性,因此没法访调用到_get__方法,这里会有一个判断的过程。
假如类属性不是描述器,对象属性是呢,也同样
class A(object): a = '' def f(self): self.a = descr('siri') print('Im A.f') >>> a = A() >>> a.f() Im A.f >>> a.a <__main__.descr object at 0x10b63c450>
因此描述器对象通常都用作类属性描述
要想制做一个只读的数据描述器,须要同时定义 __set__
和 __get__
,并在 __set__
中引起一个 AttributeError
异常。定义一个引起异常的 __set__
方法就足够让一个描述器成为数据描述器。
描述器能够直接这么调用: d.__get__(obj)
然而更常见的状况是描述器在属性访问时被自动调用。举例来讲, obj.d
会在 obj
的字典中找 d
,若是 d
定义了 __get__
方法,那么 d.__get__(obj)
会依据下面的优先规则被调用。
调用的细节取决于 obj
是一个类仍是一个实例。另外,描述器只对于新式对象和新式类才起做用。继承于 object
的类叫作新式类。
对于对象来说,方法
object.__getattribute__()
把b.x
变成type(b).__dict__['x'].__get__(b, type(b))
。具体实现是依据这样的优先顺序:数据描述器优先于实例变量,实例变量优先于非数据描述器,__getattr__()方法(若是对象中包含的话)具备最低的优先级。完整的C语言实现能够在 Objects/object.c 中 PyObject_GenericGetAttr() 查看。对于类来说,方法
type.__getattribute__()
把B.x
变成B.__dict__['x'].__get__(None, B)
。
用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__()
方法会阻止正常的描述器调用__getattribute__()
只对新式类的实例可用object.__getattribute__()
和 type.__getattribute__()
对 __get__()
的调用不同super()
返回的对象一样有一个定制的 __getattribute__()
方法用来调用描述器。调用 super(B, obj).m()
时会先在 obj.__class__.__mro__
中查找与B紧邻的基类A,而后返回 A.__dict__['m'].__get__(obj, A)
。若是不是描述器,原样返回 m
。若是实例字典中找不到 m
,会回溯继续调用 object.__getattribute__()
查找。(译者注:即在 __mro__
中的下一个基类中查找)
注意:在Python 2.2中,若是 m
是一个描述器, super(B, obj).m()
只会调用方法 __get__()
。在Python 2.3中,非数据描述器(除非是个旧式类)也会被调用。 super_getattro()
的实现细节在: Objects/typeobject.c ,[del] 一个等价的Python实如今 Guido’s Tutorial [/del] (译者注:原文此句已删除,保留供你们参考)。
以上展现了描述器的机理是在 object
, type
, 和 super
的 __getattribute__()
方法中实现的。由 object
派生出的类自动的继承这个机理,或者它们有个有相似机理的元类。一样,能够重写类的 __getattribute__()
方法来关闭这个类的描述器行为。
下面的代码中定义了一个数据描述器,每次 get
和 set
都会打印一条消息。重写 __getattribute__()
是另外一个可使全部属性拥有这个行为的方法。可是,描述器在监视特定属性的时候是颇有用的。
class RevealAccess(object): """A data descriptor that sets and returns values normally and prints a message logging their access. """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print 'Retrieving', self.name return self.val def __set__(self, obj, val): print 'Updating' , self.name self.val = val >>> class MyClass(object): x = RevealAccess(10, 'var "x"') y = 5 >>> m = MyClass() >>> m.x Retrieving var "x" 10 >>> m.x = 20 Updating var "x" >>> m.x Retrieving var "x" 20 >>> m.y 5
这个协议很是简单,而且提供了使人激动的可能。一些用途实在是太广泛以至于它们被打包成独立的函数。像属性(property), 方法(bound和unbound method), 静态方法和类方法都是基于描述器协议的。
调用 property()
是创建数据描述器的一种简洁方式,从而能够在访问属性时触发相应的方法调用。这个函数的原型:
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
下面展现了一个典型应用:定义一个托管属性(Managed Attribute) x
。
class C(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "I'm the 'x' property.")
想要看看 property()
是怎么用描述器实现的? 这里有一个纯Python的等价实现:
class Property(object): "Emulate PyProperty_Type() in Objects/descrobject.c" 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 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" self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" 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__)
当用户接口已经被受权访问属性以后,需求发生一些变化,属性须要进一步处理才能返回给用户。这时 property()
可以提供很大帮助。
例如,一个电子表格类提供了访问单元格的方式: Cell('b10').value
。 以后,对这个程序的改善要求在每次访问单元格时从新计算单元格的值。然而,程序员并不想影响那些客户端中直接访问属性的代码。那么解决方案是将属性访问包装在一个属性数据描述器中:
class Cell(object): . . . def getvalue(self, obj): "Recalculate cell before returning value" self.recalc() return obj._value value = property(getvalue)
这里注意property修饰时,属性名称和要修饰的方法名称不能重复,不然会循环调用
class Person(object): def __init__(self, name): self.name = name @property def name(self): return self.name @name.setter def name(self, name): self.name = name p = Person('kk') # 会报错循环递归过深 # File "/Users/Teron/Code/Git/Personal/Test/c.py", line 10, in name # self.name = name # RuntimeError: maximum recursion depth exceeded # 改成不同便可 class Person(object): def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, name): self._name = name p = Person('kas')
Python的面向对象特征是创建在基于函数的环境之上的。非数据描述器把二者无缝地链接起来。
类的字典把方法当作函数存储。在定义类的时候,方法一般用关键字 def
和 lambda
来声明。这和建立函数是同样的。惟一的不一样之处是类方法的第一个参数用来表示对象实例。Python约定,这个参数一般是 self, 但也能够叫 this 或者其它任何名字。
为了支持方法调用,函数包含一个 __get__()
方法以便在属性访问时绑定方法。这就是说全部的函数都是非数据描述器,它们返回绑定(bound)仍是非绑定(unbound)的方法取决于他们是被实例调用仍是被类调用。用Python代码来描述就是:
class Function(object): . . . def __get__(self, obj, objtype=None): "Simulate func_descr_get() in Objects/funcobject.c" return types.MethodType(self, obj, objtype)
下面运行解释器来展现实际状况下函数描述器是如何工做的:
>>> class D(object): def f(self, x): return x >>> d = D() >>> D.__dict__['f'] # 存储成一个function <function f at 0x00C45070> >>> D.f # 从类来访问,返回unbound method <unbound method D.f> >>> d.f # 从实例来访问,返回bound method <bound method D.f of <__main__.D object at 0x00B18C90>>
从输出来看,绑定方法和非绑定方法是两个不一样的类型。它们是在文件 Objects/classobject.c(http://svn.python.org/view/python/trunk/Objects/classobject.c?view=markup) 中用C实现的, PyMethod_Type
是一个对象,可是根据 im_self
是不是 NULL (在C中等价于 None ) 而表现不一样。
一样,一个方法的表现依赖于 im_self
。若是设置了(意味着bound), 原来的函数(保存在 im_func
中)被调用,而且第一个参数设置成实例。若是unbound, 全部参数原封不动地传给原来的函数。函数 instancemethod_call()
的实际C语言实现只是比这个稍微复杂些(有一些类型检查)。
非数据描述器为将函数绑定成方法这种常见模式提供了一个简单的实现机制。
简而言之,函数有个方法 __get__()
,当函数被看成属性访问时,它就会把函数变成一个实例方法。非数据描述器把 obj.f(*args)
的调用转换成 f(obj, *args)
。 调用 klass.f(*args)
就变成调用 f(*args)
。
下面的表格总结了绑定和它最有用的两个变种:
Transformation Called from an Object Called from a Class function f(obj, *args) f(*args) staticmethod f(*args) f(*args) classmethod f(type(obj), *args) f(klass, *args)
静态方法原样返回函数,调用 c.f
或者 C.f
分别等价于 object.__getattribute__(c, "f")
或者 object.__getattribute__(C,"f")
。也就是说,不管是从一个对象仍是一个类中,这个函数都会一样地访问到。
那些不须要 self
变量的方法适合用作静态方法。
例如, 一个统计包可能包含一个用来作实验数据容器的类。这个类提供了通常的方法,来计算平均数,中位数,以及其余基于数据的描述性统计指标。然而,这个类可能包含一些概念上与统计相关但不依赖具体数据的函数。好比 erf(x)
就是一个统计工做中常常用到的,但却不依赖于特定数据的函数。它能够从类或者实例调用: s.erf(1.5) -->.9332
或者 Sample.erf(1.5) --> .9332
.
既然staticmethod将函数原封不动的返回,那下面的代码看上去就很正常了:
>>> class E(object): def f(x): print x f = staticmethod(f) >>> print E.f(3) 3 >>> print E().f(3) 3
利用非数据描述器, staticmethod()
的纯Python版本看起来像这样:
class StaticMethod(object): "Emulate PyStaticMethod_Type() in Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return self.f
不像静态方法,类方法须要在调用函数以前会在参数列表前添上class的引用做为第一个参数。无论调用者是对象仍是类,这个格式是同样的:
>>> class E(object): def f(klass, x): return klass.__name__, x f = classmethod(f) >>> print E.f(3) ('E', 3) >>> print E().f(3) ('E', 3)
当一个函数不须要相关的数据作参数而只须要一个类的引用的时候,这个特征就显得颇有用了。类方法的一个用途是用来建立不一样的类构造器。在Python 2.3中, dict.fromkeys()
能够依据一个key列表来建立一个新的字典。等价的Python实现就是:
class Dict: . . . def fromkeys(klass, iterable, value=None): "Emulate dict_fromkeys() in Objects/dictobject.c" d = klass() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys)
如今,一个新的字典就能够这么建立:
>>> Dict.fromkeys('abracadabra') {'a': None, 'r': None, 'b': None, 'c': None, 'd': None}
用非数据描述器协议, classmethod()
的纯Python版本实现看起来像这样:
class ClassMethod(object): "Emulate PyClassMethod_Type() in Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc