通过Simeon Franklin的精彩演讲,我终于理解了描述符和属性概念,以下内容可以被视为他讲义的摘要。感谢他!
要理解属性,首先需要了解描述符,因为属性是由描述符和Python装饰器语法糖实现的。不用担心,这并不难。
什么是描述符:
- 描述符是实现了至少一个名为__get__()、__set__()和__delete__()方法的任何对象。
描述符可分为两类:
- 数据描述符实现了__get__()和__set__()。
- 非数据描述符仅实现了__get__()。
根据Python官方文档所述:
描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。
那么,什么是描述符协议?简单来说,就是当Python解释器遇到类似obj.attr的属性访问时,它将按一定顺序进行搜索以解析此.attr,并且如果此attr是描述符属性,则此描述符将在特定顺序中优先采取某些措施,并根据描述符协议将此属性访问转换为对此描述符的方法调用,可能会遮盖同名实例属性或类属性。更具体地说,如果attr是数据描述符,则obj.attr将转换为此描述符__get__方法的调用结果;如果attr不是数据描述符并且是实例属性,则将匹配此实例属性;如果attr不在以上情况中且是非数据描述符,则获取该非数据描述符__get__方法的调用结果。有关属性解析规则的完整规则,请单击此处。
现在让我们谈谈属性。如果您查看了Python的描述符文档,您可以找到纯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是一个数据描述符!
@property只是使用了Python的装饰器语法糖。
@property
def attr(self):
pass
相当于:
attr = property(attr)
因此,正如我在这个问题中发布的那样,
attr
不再是一个实例方法,而是被装饰器语法糖转换为
类属性。它是一个描述符对象属性。
它如何有资格成为类属性?
好的,现在我们解决了这个问题。
然后:
难道所有的类属性都应该对任何实例相同吗?
不!
我从
Simeon Franklin的精彩演讲中借用了一个例子。
>>> class MyDescriptor(object):
... def __get__(self, obj, type):
... print self, obj, type
... def __set__(self, obj, val):
... print "Got %s" % val
...
>>> class MyClass(object):
... x = MyDescriptor() # Attached at class definition time!
...
>>> obj = MyClass()
>>> obj.x # a function call is hiding here
<...MyDescriptor object ...> <....MyClass object ...> <class '__main__.MyClass'>
>>>
>>> MyClass.x # and here!
<...MyDescriptor object ...> None <class '__main__.MyClass'>
>>>
>>> obj.x = 4 # and here
Got 4
请注意
obj.x
及其输出。 其输出的第二个元素是
<....MyClass object ...>
,它是特定实例
obj
。简单地说,因为此属性访问已转换为__get__方法调用,并且此__get__方法将特定实例参数作为其方法签名
descr.__get__(self, obj, type=None)
所需,因此它可以根据被调用的实例返回不同的值。
注:我的英文解释可能不够清晰,因此我强烈建议您查看Simeon Franklin的笔记和Python的描述符HowTo。