何时以及如何在 python 中使用内置函数 property()

Posted

技术标签:

【中文标题】何时以及如何在 python 中使用内置函数 property()【英文标题】:When and how to use the builtin function property() in python 【发布时间】:2010-12-05 23:43:12 【问题描述】:

在我看来,除了一点语法糖,property() 并没有什么好处。

当然,能够写成 a.b=2 而不是 a.setB(2) 是件好事,但隐藏 ab=2 不是一个简单的赋值这一事实似乎是个麻烦事,因为可能会发生一些意想不到的结果,例如a.b=2 实际上导致a.b1。或引发异常。或者性能问题。或者只是令人困惑。

你能给我一个具体的例子来很好地使用它吗? (用它来修补有问题的代码不算数;-)

【问题讨论】:

你错了。 .setB() 是针对没有实际属性的语言的补丁。 Python、C#和其他一些确实有属性,解决方法是一样的。 我见过 java 人更糟糕的事情,那就是为每个属性编写 getter 和 setter - 以防万一,因为以后重构一切都很痛苦。 @gnibbler,在不支持属性的语言中(或者至少有点等价的可能性,例如 Python 的 __getattr__/__setattr__,它大大早于属性),没有太多其他“Java伙计们”(在任何这些语言中,包括 C++!-)都可以做到,以保持封装。 【参考方案1】:

在依赖于 getter 和 setter 的语言中,例如 Java,除了返回逻辑属性 @ 的当前值之外,如果 x.getB() 做了任何事情,那将是惊人的。 987654322@,或者如果x.setB(2) 做了任何事情,但需要进行少量内部工作以使x.getB() 返回2

但是,对于这种预期行为,没有语言强加的保证,即编译器对名称以getset 开头的方法体的约束:相反,它是留给常识、社会习俗、“风格指南”和测试。

x.b 访问和赋值(例如 x.b = 2)在具有属性的语言(包括但不限于 Python 的一组语言)中的行为完全相同相同至于 Java 中的 getter 和 setter 方法:同样的期望,同样缺乏语言强制保证。

属性的第一个优势是语法和可读性。必须写,例如,

x.setB(x.getB() + 1)

而不是显而易见的

x.b += 1

呼喊着向众神报仇。在支持属性的语言中,绝对没有充分的理由强迫该类的用户经历这种拜占庭样板的旋转,从而影响他们代码的可读性而没有任何好处。

特别是在 Python 中,使用属性(或其他描述符)代替 getter 和 setter 还有一个很大的好处:如果并且当您重新组织类以便不再需要底层的 setter 和 getter 时,您可以(无需破坏类的已发布 API)简单地消除这些方法和依赖于它们的属性,使 b 成为 x 类的正常“存储”属性,而不是通过计算获得和设置的“逻辑”属性。

在 Python 中,直接(可行时)而不是通过方法执行操作是一项重要的优化,系统地使用属性使您能够在可行的情况下执行此优化(始终直接公开“正常存储的属性”,并且只公开那些确实需要的属性)通过方法和属性访问和/或设置时计算)。

因此,如果您使用 getter 和 setter 而不是属性,除了影响用户代码的可读性之外,您无缘无故地浪费机器周期(以及在这些过程中进入计算机的能量)循环;-),再次没有任何充分的理由。

您反对属性的唯一论据是例如“外部用户通常不会期望由于分配而产生任何副作用”;但是您错过了这样一个事实,即同一用户(在诸如 Java 之类的 getter 和 setter 普遍存在的语言中)也不会期望(可观察到的)“副作用”作为调用 setter 的结果(对于 getter 来说甚至更少) ;-)。它们是合理的期望,作为课程作者,您可以尝试适应它们——无论您的 setter 和 getter 是直接使用还是通过属性使用,都没有区别。如果您的方法具有可观察到的重要副作用,请不要将它们命名为 getThissetThat,并且不要通过属性使用它们。

属性“隐藏实现”的抱怨是完全没有道理的:OOP 的大部分所有都是关于实现信息隐藏——让一个类负责向外部世界呈现一个逻辑接口并实现它内部尽可能。 Getter 和 setter 就像属性一样,是实现这一目标的工具。属性只是在这方面做得更好(在支持它们的语言中;-)。

【讨论】:

另外,看看 C++ 的运算符重载。同样的“外部用户不会期望......”论点适用。在某些情况下,您只是不想让用户知道。在这些情况下,您需要确保不会让用户感到惊讶。 @Oren,好点子,Python 的运算符重载很像 C++ 的(赋值不能重载,因为它不是运算符,&c,但一般概念是相似的)。这取决于编码人员的礼貌、纪律和常识,以避免例如__add__ 改变自我和/或做一些与加法无关的事情——这并不意味着操作符重载在有品位和明智的使用时是一件坏事(尽管 Java 的设计者不同意,因为他们故意将其排除在 之外)他们的语言!-)。 "...您可以(不破坏类的已发布 API)简单地消除那些方法和依赖它们的属性,使 x 类的正常“存储”属性而不是“逻辑”一个通过计算获得和设置。” - ???我不明白这一点,我知道如果您更改构成属性的底层内部属性,则该属性提供的公共“接口”不必更改,但 getter/setter 也是如此。两者都只是对象内部值之上的抽象层。你想在这里说什么? @Adam Parkin 如果客户端程序调用“GetTaxRate()”,您将无法在不破坏向后可比性的情况下删除方法“GetTaxRate”。但是,您可以在不破坏向后兼容性的情况下删除“@property”,因为直接访问和“@property”使用相同的语法。【参考方案2】:

这个想法是让您避免在真正需要之前编写 getter 和 setter。

所以,开始你写:

class MyClass(object):
    def __init__(self):
        self.myval = 4

显然你现在可以写myobj.myval = 5

但后来,您决定确实需要一个二传手,因为您想同时做一些聪明的事情。但是您不想更改使用您的类的所有代码 - 因此您将 setter 包装在 @property 装饰器中,一切正常。

【讨论】:

你的例子正是我所说的“用它来修补有问题的代码”的意思......也许“有问题”不是在这里使用的正确词,但它是一个补丁 IMO。跨度> 我支持@olamundo 的想法:我经常看到变量变成@property 在setter/getter 中添加一些逻辑而不破坏兼容性。这故意给@property 带来副作用,并且让未来的程序员在相同的代码上感到困惑。【参考方案3】:

但隐藏 a.b=2 不是 a 简单的分配看起来像一个食谱 找麻烦

不过,你并没有隐瞒这个事实;这个事实从一开始就没有。这是python——一种高级语言;不是组装。其中很少有“简单”语句可以归结为单个 CPU 指令。将简单读入作业中,就是读懂不存在的东西。

当您说 x.b = c 时,您可能只想到“无论刚刚发生什么,x.b 现在应该是 c”。

【讨论】:

我同意你所说的,但我仍然认为它是“隐藏”内部工作原理,因为外部用户通常不会期望由于分配而产生任何副作用。跨度> 那是真正的诺姆。但另一方面,OOP 就是让对象都是黑盒子,除了它们所呈现的接口,所以人们可能应该假设表面之下正在发生奇怪的、神奇的事情;) a.b = 2 几乎不比 x = 2 少。如果它们的实现方式不同,那就这样吧,OOP 的全部意义在于隐藏实现细节。如果 a.b = 2 将某些值设置为 490,那么这是一个令人讨厌的副作用,无论如何 a.setB(2) 都无法解决。如果您在看到 a.b = 2 时隐含地认为它应该是一个非常快的操作,那么您应该意识到您在 Python 中并且已经比 C 慢了至少一个数量级。【参考方案4】:

一个基本的原因就是它看起来更好。它更pythonic。特别是对于图书馆。 something.getValue() 看起来不如 something.value 好

在 plone(一个相当大的 CMS)中,您曾经拥有 document.setTitle(),它会做很多事情,例如存储值、再次索引它等等。只是做 document.title = 'something' 更好。无论如何,您都知道幕后发生了很多事情。

【讨论】:

【参考方案5】:

你是对的,它只是语法糖。根据您对有问题的代码的定义,它可能没有很好的用途。

假设您有一个在您的应用程序中广泛使用的类 Foo。现在这个应用程序已经变得相当大了,可以说它是一个非常流行的网络应用程序。

您发现 Foo 导致了瓶颈。也许可以向 Foo 添加一些缓存以加快速度。使用属性可以让您在不更改 Foo 之外的任何代码或测试的情况下做到这一点。

是的,当然这是有问题的代码,但您只是节省了很多 $$ 快速修复它。

如果 Foo 在您拥有成百上千用户的库中怎么办?好吧,当他们升级到 Foo 的最新版本时,您不必告诉他们进行昂贵的重构。

发行说明有一个关于 Foo 的项目,而不是段落移植指南。

经验丰富的 Python 程序员除了 a.b==2 之外,对 a.b=2 的期望并不高,但他们知道即使这样也可能不是真的。课堂内发生的事情是它自己的事情。

【讨论】:

【参考方案6】:

这是我的一个老例子。我包装了一个 C 库,它具有“void dt_setcharge(int atom_handle, int new_charge)”和“int dt_getcharge(int atom_handle)”等功能。我想在 Python 级别执行“atom.charge = atom.charge + 1”。

“属性”装饰器让这一切变得简单。比如:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

10 年前,当我编写这个包时,我不得不使用 __getattr__ 和 __setattr__ 这使得它成为可能,但实现起来更容易出错。

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value

【讨论】:

【参考方案7】:

getter 和 setter 有很多用途,它们非常有用,因为它们对代码是透明的。让对象 Something 具有属性 height,您将值分配为 Something.height = 10,但如果 height 具有 getter 和 setter,那么在您分配该值时,您可以在过程中做很多事情,例如验证最小值或最大值值,例如因为高度变化而触发事件,根据新的高度值自动设置其他值,所有这些都可能在分配Something.height值的那一刻发生。请记住,您不需要在代码中调用它们,它们会在您读取或写入属性值时自动执行。在某些方面,它们类似于事件过程,当属性 X 更改值以及读取属性 X 值时。

【讨论】:

【参考方案8】:

当您尝试在重构中用委托替换继承时,它很有用。以下是一个玩具示例。 StackVector 中的子类。

class Vector:
    def __init__(self, data):
        self.data = data

    @staticmethod
    def get_model_with_dict():
        return Vector([0, 1])


class Stack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()
        self.data = self.model.data


class NewStack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()

    @property
    def data(self):
        return self.model.data

    @data.setter
    def data(self, value):
        self.model.data = value


if __name__ == '__main__':
    c = Stack()
    print(f'init: c.data') #init: [0, 1]

    c.data = [0, 1, 2, 3]
    print(f'data in model: c.model.data vs data in controller: c.data') 
    #data in model: [0, 1] vs data in controller: [0, 1, 2, 3]

    c_n = NewStack()
    c_n.data = [0, 1, 2, 3]
    print(f'data in model: c_n.model.data vs data in controller: c_n.data') 
    #data in model: [0, 1, 2, 3] vs data in controller: [0, 1, 2, 3]

请注意,如果您确实使用直接访问而不是属性,self.model.data 不等于 self.data,这超出了我们的预期。

您可以将代码 before __name__=='__main__' 作为库。

【讨论】:

以上是关于何时以及如何在 python 中使用内置函数 property()的主要内容,如果未能解决你的问题,请参考以下文章

python内置类型详细解释

python之匿名函数以及在内置函数中的使用

python内置函数

如何在python中使用lambda函数[重复]

Python__内置函数

05python 的内置函数以及匿名函数(python函数)