为了账号安全,请及时绑定邮箱和手机立即绑定

为什么属性类属性在 Python 中?

为什么属性类属性在 Python 中?

MMTTMM 2021-06-03 05:11:33
我正在阅读Fluent Python 第 19 章 > A Proper Look at Properties,我对以下单词感到困惑:属性始终是类属性,但它们实际上管理类实例中的属性访问。示例代码是: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,属性,是一个实例方法,它返回的值在实例之间是不同的。它如何有资格成为类属性?对于任何实例,所有类属性不是都应该相同吗?我想我误解了一些东西,所以我希望得到一个正确的解释。谢谢!
查看完整描述

3 回答

?
青春有我

TA贡献1784条经验 获得超8个赞

通过Simeon Franklin的精彩演讲,我终于明白了描述符和属性的概念,下面的内容可以看作是他的讲义的总结。感谢他!


要了解属性,首先需要了解描述符,因为属性是由描述符和python 的装饰器语法糖实现的。别担心,这并不难。


什么是描述符:


一个描述符是的方法实现了至少一个命名__get任何对象__(),__set __(),和__delete __()。

描述符可以分为两类:


甲数据描述符同时实现__get __()和__set __()。

阿非数据描述符仅实现__get __()。

根据python 的 HowTo:


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


那么描述符协议是什么呢?基本上来说,就是说当Python解释器遇到像这样的属性访问时obj.attr,它会按某种顺序搜索来解决这个问题.attr,如果这attr是一个描述符属性,那么这个描述符会在这个特定的顺序和这个属性访问中占有一定的优先级将根据描述符协议转换为对该描述符的方法调用,可能隐藏同名实例属性或类属性。更具体地说,如果attr是一个数据描述符,那么obj.attr会被翻译成这个描述符的__get__方法的调用结果;如果attr不是数据描述符而是实例属性,则匹配此实例属性;如果attr不在上面,而且是一个非数据描述符,我们得到这个非数据描述符的 __get__ 方法的调用结果。可以在此处找到有关属性解析的完整规则。


现在让我们谈谈财产。如果您查看过Python 的描述符 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 是一个数据描述符!


@property 只是使用 python 的装饰器语法糖。


@property

def attr(self):

    pass

相当于:


attr = property(attr)

所以,attr不再是我在 thie question 中发布的实例方法,而是像作者所说的那样被装饰器语法糖翻译成一个类属性。它是一个描述符对象属性。


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


好的,我们现在解决了。


然后:


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


不!


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


查看完整回答
反对 回复 2021-06-09
  • 3 回答
  • 0 关注
  • 177 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信