语法简析
1、一般来说,描述器(descriptor)是一个有”绑定行为”的对象属性(object attribute),它的属性访问被描述器协议方法重写。这些方法是
__get__()
、 __set__()
和 __delete__()
。
如果一个对象定义了以上任意一个方法,它就是一个描述器。而描述器协议的具体形式如下:
descr.__get__(self, obj, type=None) --> value descr.__set__(self, obj, value) --> None descr.__delete__(self, obj) --> None
2、描述器本质上是一个类对象,该对象定义了描述器协议三种方法中至少一种。
3、而这三种方法只有当类的实例出现在一个所有者类(owner class)之内时才有效,也就是说,描述器必须出现在所有者类或其父类的字典 __dict__ 里。
4、以上提到了两个类,一是定义了描述器协议的描述器类,另一个是使用描述器的所有者类。
5、描述器往往以装饰器的方式被使用,导致二者常被混淆。
6、描述器类和不带参数的装饰器类一样,都传入函数对象作为参数,并返回一个类实例;所不同的是,装饰器类返回 callable 的实例,描述器则返回描述器实例。
举例说明
一、@Property
Python 内置的property 函数可以说是最著名的描述器之一,几乎所有讲述描述器的文章都会拿它做例子。
property 是用 C 实现的,不过这里有一份等价的 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 if doc is None and fget is not None: doc = fget.__doc__ 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 怎么用呢?看下面的例子:
class C(object): def __init__(self): self._x = None @Property def x(self): """I‘m the ‘x‘ property.""" return self._x @x.setter def x(self, value): assert value > 0 self._x = value @x.deleter def x(self): del self._x
结合源代码和用法来分析 Property :
@Property的用法就是一个装饰器。我们可以将其等价转化为:
x = Property(x)
函数 x
作为位置参数被赋给 Property.__init__() 的 fget
,得到新的 x
已经不是个函数而是个完整实现了 __get__() 方法的描述器实例了。
二、@x.setter
@x.setter 的用法略有不同。它实际上是利用上面定义的描述器实例
x
的 setter
方法,重新创建了新的实例。这时变量 x
再次被更新,指向了一个完整实现 __get__()
和 __set__()
方法的新描述器。传入 setter
方法的函数名必须是 x
,否则如果是 y
,按照装饰器的性质,y = x.setter(y)
新描述器就被 y
引用了,与需求不符。