我正在阅读Fluent Python第19章>正确查看属性,我对以下单词感到困惑:
属性始终是类属性,但它们实际上管理类实例中的属性访问.
示例代码是:
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight # <1>
self.price = price
def subtotal(self):
return self.weight * self.price
@property # <2>
def weight(self): # <3>
return self.__weight # <4>
@weight.setter # <5>
def weight(self, value):
if value > 0:
self.__weight = value # <6>
else:
raise ValueError('value must be > 0') # <7>
Run Code Online (Sandbox Code Playgroud)
根据我以前的经验,类属性属于类本身并由所有实例共享.但是在这里,weight,属性是一个实例方法,它返回的值在实例之间是不同的.如何成为类属性?不是所有类属性对于任何实例都应该是相同的吗?
我想我误解了一些东西,所以我希望得到正确的解释.谢谢!
通过Simeon Franklin的精彩演讲,我终于理解了描述符和属性概念,以下内容可以看作是对他的讲义的总结。感谢他!
\n\n要理解属性,首先需要理解描述符,因为属性是由描述符和Python的装饰器语法糖实现的。别担心,这并不难。
\n\n什么是描述符:
\n\n描述符可以分为两类:
\n\n\n\n\n描述符是具有\xe2\x80\x9c绑定行为\xe2\x80\x9d的对象属性,其属性访问已被描述符协议中的方法覆盖。
\n
那么什么是描述符协议呢?基本上来说,它只是说,当Python解释器遇到像obj.attr\xef\xbc\x8cit这样的属性访问时,将按某种顺序搜索来解决这个问题.attr,如果这attr是一个描述符属性,那么这个描述符将优先考虑此特定顺序和此属性访问将根据描述符协议转换为对此描述符的方法调用,可能会隐藏同名实例属性或类属性。更具体地说,如果attr是一个数据描述符,那么obj.attr将被翻译成该描述符的 __get__ 方法的调用结果;如果attr不是数据描述符并且是实例属性,则匹配该实例属性;如果attr不在上面,并且它是一个非数据描述符,我们就得到这个非数据描述符的 __get__ 方法的调用结果。有关属性解析的完整规则可以在此处找到。
现在我们来谈谈财产。如果您查看过Python 描述符 HowTo,您可以找到属性的纯 Python 版本实现:
\n\nclass Property(object):\n "Emulate PyProperty_Type() in Objects/descrobject.c"\n\n def __init__(self, fget=None, fset=None, fdel=None, doc=None):\n self.fget = fget\n self.fset = fset\n self.fdel = fdel\n if doc is None and fget is not None:\n doc = fget.__doc__\n self.__doc__ = doc\n\n def __get__(self, obj, objtype=None):\n if obj is None:\n return self\n if self.fget is None:\n raise AttributeError("unreadable attribute")\n return self.fget(obj)\n\n def __set__(self, obj, value):\n if self.fset is None:\n raise AttributeError("can\'t set attribute")\n self.fset(obj, value)\n\n def __delete__(self, obj):\n if self.fdel is None:\n raise AttributeError("can\'t delete attribute")\n self.fdel(obj)\n\n def getter(self, fget):\n return type(self)(fget, self.fset, self.fdel, self.__doc__)\n\n def setter(self, fset):\n return type(self)(self.fget, fset, self.fdel, self.__doc__)\n\n def deleter(self, fdel):\n return type(self)(self.fget, self.fset, fdel, self.__doc__)\nRun Code Online (Sandbox Code Playgroud)\n\n显然\xef\xbc\x8cproperty是一个数据描述符!
\n\n@property 只是使用 python 的装饰器语法糖。
\n\n@property\ndef attr(self):\n pass\nRun Code Online (Sandbox Code Playgroud)\n\n相当于:
\n\nattr = property(attr)\nRun Code Online (Sandbox Code Playgroud)\n\n因此,attr它不再是我在问题中发布的实例方法,而是如作者所说由装饰器语法糖转换为类属性。它是一个描述符对象属性。
\n\n\n它如何有资格成为类别属性?
\n
好的,我们现在解决了。
\n\n然后:
\n\n\n\n\n对于任何实例来说,所有类属性不是都应该相同吗?
\n
不!
\n\n我从西蒙·富兰克林的精彩演讲中借用了一个例子。
\n\n>>> class MyDescriptor(object):\n... def __get__(self, obj, type):\n... print self, obj, type\n... def __set__(self, obj, val):\n... print "Got %s" % val\n...\n>>> class MyClass(object):\n... x = MyDescriptor() # Attached at class definition time!\n...\n>>> obj = MyClass()\n>>> obj.x # a function call is hiding here\n<...MyDescriptor object ...> <....MyClass object ...> <class \'__main__.MyClass\'>\n>>>\n>>> MyClass.x # and here!\n<...MyDescriptor object ...> None <class \'__main__.MyClass\'>\n>>>\n>>> obj.x = 4 # and here\nGot 4\nRun Code Online (Sandbox Code Playgroud)\n\n注意obj.x及其输出。其输出中的第二个元素是<....MyClass object ...>。这是具体实例obj。简而言之,因为这个属性访问已经被翻译成一个 __get__ 方法调用,并且这个 __get__ 方法按照其方法签名的要求获取特定的实例参数descr.__get__(self, obj, type=None),所以它可以根据调用它的实例返回不同的值。
注意:我的英文解释可能不够清楚,所以我强烈建议你看看 Simeon Franklin 的笔记和 Python 的描述符 HowTo。
\n