社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

为什么python中有属性类属性?

Bicheng • 6 年前 • 1974 次点击  

我在读书 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>

根据我以前的经验,类属性属于类本身,并且由所有实例共享。但是这里的weight属性是一个实例方法,它返回的值在实例之间是不同的。它如何有资格成为一个类属性?对于任何实例,所有的类属性都应该是相同的,不是吗?

我想我误解了一些事情,所以我希望得到一个正确的解释。谢谢!

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/38693
 
1974 次点击  
文章 [ 5 ]  |  最新文章 6 年前
Bicheng
Reply   •   1 楼
Bicheng    7 年前

我最终通过 Simeon Franklin's excellent presentation 以下内容可作为他讲稿的总结。谢谢他!

要理解属性,首先需要了解描述符,因为属性是由描述符和Python的decorator语法糖实现的。别担心,没那么难。

什么是描述符:

  • 描述符 是实现至少一个名为u get_uuu()、u set_uuu()和uu delete_uuu()的方法的任何对象。

描述符可以分为两类:

  • 数据描述符 实现uu get_uu()和uu set_uu()。
  • 非数据描述符 仅实现uu get_uu()。

根据 python's HowTo :

描述符是具有绑定行为的对象属性,其属性访问被描述符协议中的方法覆盖。

那么什么是描述符协议呢?基本上,它只是说当Python解释器遇到一个属性访问时 obj.attr _¼它将以某种顺序搜索以解决此问题。 .attr 如果这个 attr 是一个描述符属性,那么这个描述符将以这个特定的顺序具有一定的优先级,并且这个属性访问将根据描述符协议被转换成对这个描述符的方法调用,可能会隐藏一个同名实例属性或类属性。更具体地说,如果 阿特尔 是一个数据描述符,那么 对象属性 将转换为此描述符的get方法的调用结果;如果 阿特尔 不是数据描述符并且是实例属性,将匹配此实例属性;如果 阿特尔 不在上面,它是一个非数据描述符,我们得到这个非数据描述符的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__)

显然,属性是一个数据描述符!

@属性只使用python的decorator语法糖。

@property
def attr(self):
    pass

相当于:

attr = property(attr)

所以, 阿特尔 不再是我在问题中发布的实例方法,而是转换为 类属性 按作者所说的装饰句法糖。它是一个描述符对象属性。

它如何有资格成为一个类属性?

好的,我们现在解决了。

然后:

对于任何实例,所有的类属性都应该是相同的,不是吗?

不!

我偷了一个例子 西蒙·富兰克林的精彩演讲 .

>>> 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。

Petronella
Reply   •   2 楼
Petronella    7 年前

我认为这个例子是错误的, 初始化 应该是这样的:

def __init__(self, description, weight, price):
    self.description = description
    self.__weight = weight  # <1> 
    self.__price = price

self.\u weight和self.\u price是属性隐藏在类中的内部属性

blue_note
Reply   •   3 楼
blue_note    7 年前

你没有误解。别担心,继续读吧。这将在下一章中阐明。

同一本书在第20章中解释说,它们可以是类属性,因为 描述符协议 . 文档解释了 properties are implemented as descriptors .

从示例中可以看到,属性实际上是类属性(方法)。当调用时,它们获取对实例的引用,并写入/读取其底层 __dict__ .

bruno desthuilliers
Reply   •   4 楼
bruno desthuilliers    7 年前

根据我以前的经验,类属性属于类本身,并且由所有实例共享。

这是正确的。

但是这里的weight属性是一个实例方法

不,这是一个 property 对象。当你这样做的时候:

@decorator
def func():
    return 42

它实际上是对

def func():
    return 42

func = decorator(func)

失去 def 语句被执行,函数对象被创建,但它没有绑定到它的名称,而是被传递到 decorator 可调用,并且名称绑定到 decorator() 返回。

在这种情况下,装饰师是 财产 类本身,因此 weight 属性是 财产 实例。你可以自己检查一下 LineItem.weight (将返回 财产 对象本身)。

它返回的值在实例之间是不同的。

是的,当然,这有什么奇怪的? LineItem.subtotal 也是一个类属性(和所有方法一样),但它从调用它的实例返回值(该实例作为 self 参数)。

它如何有资格成为一个类属性?对于任何实例,所有的类属性都应该是相同的,不是吗?

类属性对于类的所有实例都是相同的,是的。只有一个单人间 subtotal 函数的所有实例 LineItem .

财产 主要是使一个函数(如果指定了setter,则是一对函数)看起来像一个普通属性的快捷方式,因此当您键入 mylinitem.weight ,真正执行的是 LineItem.weight.fget(mylineitem) ,在哪里 fget 你装饰的是吸气剂的功能吗 @property . 其背后的机制称为 "descriptor protocol" ,也用于转动 mylineitem.subtotal() 进入之内 LineItem.subtotal(mylineitem) (python函数实现描述符协议以返回“method”对象,这些对象本身就是函数和当前实例的包装器,并将实例作为函数调用的第一个参数插入)。

所以属性不是类属性,你只需要一个 财产 实例为类的所有实例提供“服务”,而且,属性(如所有描述符fwiw)实际上必须是类属性才能按预期工作,因为描述符协议仅在类属性上调用(由于fu负责“计算”的NCtion将获取实例作为参数)。

alxwrd
Reply   •   5 楼
alxwrd    7 年前

区别是因为当你定义一个 @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 “。意义 @财产 如果函数与 function = property(function) . 这也适用于在类内部定义的函数,但现在该函数是类命名空间的一部分。

class TestClass:
    @property
    def foo(self):
        return "foo"
    # ^ is the same as:
    def bar(self):
         return "bar"
    bar = property(bar)

一个很好的解释 property 在python中可以找到: https://stackoverflow.com/a/17330273/7220776