我正在阅读 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>
根据我以前的经验,类属性属于类本身,并由所有实例共享。但是在这里,权重(即属性)是一个实例方法,因此实例之间的返回值是不同的。如何才能成为类别属性?不是所有实例的所有类属性都应该相同吗?
我认为我误会了一些东西,因此希望得到正确的解释。谢谢!
答案 0 :(得分:3)
之所以有区别,是因为当您在类上定义@property
时,该属性对象成为该类的属性。而当您针对类的实例(在__init__
方法中)定义属性时,该属性仅针对该对象存在。如果这样做,可能会造成混淆:
>>> dir(LineItem)
['__class__', ..., '__weakref__', 'subtotal', 'weight']
>>> item = LineItem("an item", 3, 1.12)
>>> dir(item)
['__class__', ..., '__weakref__', 'description', 'price', 'subtotal', 'weight']
请注意,subtotal
和 weight
都作为类的属性存在。
我认为还值得注意的是,当您定义一个类时,该类下的代码将被执行。这包括定义变量(然后成为类属性),定义函数以及其他任何东西。
>>> import requests
>>> class KindOfJustANamespace:
... text = requests.get("https://example.com").text
... while True:
... break
... for x in range(2):
... print(x)
...
0
1
>>> KindOfJustANamespace.text
'<!doctype html>\n<html>\n<head>\n <title>Example Domain...'
@decorator
只是“ syntactic sugar”。如果与@property
相同,则在函数上具有function = property(function)
的含义。这同样适用于在类内部定义的函数,但是现在该函数是该类名称空间的一部分。
class TestClass:
@property
def foo(self):
return "foo"
# ^ is the same as:
def bar(self):
return "bar"
bar = property(bar)
在https://stackoverflow.com/a/17330273/7220776
中可以找到关于Python中property
的很好的解释。
答案 1 :(得分:2)
根据我以前的经验,类属性属于类本身,并由所有实例共享。
是的。
但是这里的权重属性是一个实例方法
否,它是property
对象。当您这样做时:
@decorator
def func():
return 42
实际上是语法糖
def func():
return 42
func = decorator(func)
IOW def
语句已执行,函数对象已创建,但并没有与其名称绑定,而是传递给了decorator
可调用对象,并且该名称已绑定到任何{{1} } 回到。
在这种情况下,装饰器是decorator()
类本身,因此property
属性是weight
实例。您可以通过检查property
(这将返回LineItem.weight
对象本身)来自己检查。
,实例之间的返回值不同。
当然可以,这令人惊讶吗? property
也是一个类属性(就像所有方法一样),但是它从被调用的实例返回值(将其作为LineItem.subtotal
参数传递给函数)。
如何成为类别属性?不是所有实例的所有类属性都应该相同吗?
对于一个类的所有实例,类属性都是相同的,是的。 self
的所有实例只有一个subtotal
函数。
LineItem
主要是使一个函数(或指定一对setter的一对函数)看起来像是普通属性的快捷方式,因此当您键入property
时,实际执行的是是mylinitem.weight
,其中LineItem.weight.fget(mylineitem)
是用fget
装饰的getter函数。其背后的机制称为"descriptor protocol",它也用于将@property
变成mylineitem.subtotal()
(Python函数实现描述符协议以返回“方法”对象,这些对象本身就是包装器)函数和当前实例,然后将该实例作为函数调用的第一个参数插入)。
因此,不必奇怪属性是类属性-您只需要一个LineItem.subtotal(mylineitem)
实例即可“服务”该类的所有实例-而且,属性-像所有描述符FWIW一样-必须实际上是由于描述符协议仅在类属性上调用,因此可以按预期方式工作(“按实例”计算的属性没有用例,因为负责“计算”的功能会将实例作为参数)。
答案 2 :(得分:0)
您没有误会。不用担心,请继续阅读。在下一章中将变得清楚。
同一本书在第20章中解释说,由于 descriptor协议,它们可以是类属性。该文档介绍了properties are implemented as descriptors的用法。
从示例中可以看到,属性实际上是类属性(方法)。在被调用时,它们获得对该实例的引用,并对其底层__dict__
进行写入/读取。
答案 3 :(得分:0)
我认为该示例是错误的, init 应该看起来像这样:
def __init__(self, description, weight, price):
self.description = description
self.__weight = weight # <1>
self.__price = price
self .__ weight和self .__ price是属性隐藏在类中的内部属性
答案 4 :(得分:0)
我终于通过Simeon Franklin's excellent presentation理解了描述符和属性的概念,以下内容可以作为他的讲义的摘要。多亏他!
要了解属性,首先需要了解描述符,因为属性是由描述符和python的装饰器语法糖实现的。不用担心,这并不困难。
什么是描述符:
描述符可以分为两类:
描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。
那么描述符协议是什么?基本上来说,这就是说,当Python解释器遇到obj.attr
之类的属性访问时,它将以某种顺序搜索以解决此.attr
,并且如果此attr
是描述符属性,那么此描述符将按此特定顺序优先处理,并且此属性访问将根据描述符协议转换为对该描述符的方法调用,可能会遮盖同名实例属性或类属性。更具体地说,如果attr
是数据描述符,那么obj.attr
将被转换为该描述符的__get__方法的调用结果;如果attr
不是数据描述符,而是实例属性,则该实例属性将被匹配;如果attr
不在上面,并且它是一个非数据描述符,我们将获得此非数据描述符的__get__方法的调用结果。有关属性解析的完整规则,请参见here。
现在让我们谈谈财产。如果您查看过Python' descriptor HowTo,则可以找到属性的纯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仅使用python的装饰器语法糖。
@property
def attr(self):
pass
等效于:
attr = property(attr)
因此,attr
不再是我在该问题中发布的实例方法,而是如修饰符所说的那样,由修饰符语法糖转换为 class属性。这是一个描述符对象属性。
如何成为类别属性?
好的,我们现在解决了。
然后:
不是所有实例的所有类属性都应该相同吗?
不!
我从Simeon Franklin's excellent presentation窃取了一个例子。
>>> 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。