1.6. 属性(properties)
调用 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)
1.7. 函数和方法
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语言实现只是比这个稍微复杂些(有一些类型检查)。

1.8. 静态方法和类方法
非资料描述器为将函数绑定成方法这种常见模式提供了一个简单的实现机制。

简而言之,函数有个方法 __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