起步
property
是 Python 内置的功能,常用来修饰类方法,用于已访问属性的方式调用函数。
class C(object):
def __init__(self):
self._x = 'Tom'
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
c = C()
print(c.x) # Tom
c.x = 'Tony'
print(c.x) # Tony
尽管 property
的实现是 C 实现,但仍不妨碍探究它的实现原理,本文最后也会给出它的纯 Python 版本的实现。
描述符对象
为了能够实现访问属性就调用某个函数,这里将利用 描述符对象 作为本文的实现起点,当某个类定义了 __get__
方法后,通过其方法名称可以直接调用 __get__
,例如:
class Desc:
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.name
class A:
x = Desc('Tom')
a = A()
print(a.x) # 打印了 'Retrieving'
从这点来看,如果我们自行实现 property
,那它将会是类而不是函数,同样的为了能够完成属性的赋值操作,该类还要设置 __set__
函数。
setter 函数的实现
这个的实现需要脑子转个弯。对于修饰符 @x.setter
,因为 x
已经是 property()
的实例,所以我们要完成的 property
要实现 setter
函数,那函数体会是什么呢?
函数体也是要返回描述符对象,并该对象是有 __set__
的。那 property
不就正好满足吗,所以这里的处理方式是 setter
函数会返回一个新的 property
实例。
property 的简易实现
基于上述分析,对于开头中的实例代码可运行的简易版本:
class property:
def __init__(self, fget=None, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, inst, owner=None):
if inst is None:
return self
return self.fget(inst)
def __set__(self, inst, value):
self.fset(inst, value)
def setter(self, fset):
return property(self.fget, fset)
property 的完整实现
这个基本是依据 C 实现的纯 Python 版本,纯 C 实现在文件 Objects/descrobject.c
中。
Python 实现版本:
class property:
"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__)
在创建新的 proptery
实例中使用的是 type(self)(...)
,这是因为考虑到了 proptery
可能被继承。
总结
proptery
主要依赖于描述符的机制。proptery
内置也成为了 Python 的一个特性,它的内部实现原理很简单,但在应用上却很方面,可读性也十分友好。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。