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


74

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

当然,能够编写a.b=2而不是很好a.setB(2),但是隐藏ab = 2不是简单赋值的事实看起来像是麻烦的秘诀,或者是因为可能发生某些意外结果,例如a.b=2实际上导致a.b成为1。或引发异常。还是性能问题。或只是令人困惑。

您能给我一个具体示例以说明用法吗?(用它来修补有问题的代码不计算在内;-)


12
你理解错了。.setB()是不具有实际属性的语言的修补程序。Python,C#和其他一些确实具有属性,并且解决方案相同。
liori

5
我见过Java伙计们更糟糕的事情,那就是为每个属性编写getter和setter方法-以防万一,因为稍后再重构所有内容很痛苦。
John La Rooy

4
@gnibbler,在不支持属性的语言中(或至少在某种程度上等效的可能性,例如Python的__getattr__/ __setattr__,它们早于属性),没有太多“ Java专家”(在任何这些语言中,包括C ++!-)可以做,以保留封装。
亚历克斯·马丁里

Answers:


138

在像Java这样的依赖getter和setter的语言中,除了他们说的话,他们不应该做任何事情,也不希望他们做任何事情-如果x.getB()除了返回逻辑属性的当前值以外,什么都不做b,或者是否x.setB(2)做了任何事,都将是令人惊讶的需要少量的内部工作才能获得x.getB()回报2

但是,没有关于这种预期行为的语言保证,即,名称以get或开头的方法主体受到编译器的限制set:而是由常识,社会习惯,“样式指南”和测试决定。

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

属性的首个胜利是语法和可读性。不得不写,例如

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也不要通过属性使用它们。

该属性“隐藏实现”的抱怨是没有道理的全资:大部分所有的面向对象是有关实现信息隐藏-制造类负责提出一个逻辑接口与外部世界和内部实现它,因为它最能。就像属性一样,getter和setter是实现此目标的工具。属性只是做得更好(使用支持它们的语言;-)。


4
另外,看看C ++的运算符重载。同样的“外部用户不会期望...”参数也适用。在某些情况下,您只是不希望用户知道。在这种情况下,请确保您不会让用户感到惊讶。
Oren S

4
@Oren,很好,Python的运算符重载与C ++的重载非常相似(赋值不能重载,因为它不是运算符&c,但总体概念相似)。避免例如__add__更改自我和/或做与加法没有任何远程关系的事情取决于编码员的礼貌,纪律和常识-这并不意味着操作员重载在有礼貌和明智地使用时是一件坏事(尽管Java的设计师不同意,因为他们故意留出来的自己的语言- !)。
亚历克斯·马丁里

“ ...您可以(不破坏类的已发布API)简单地消除那些方法和依赖于它们的属性,从而使x的类成为普通的“存储”属性,而不是通过计算获得并设置的“逻辑”属性。” -??? 我不明白这一点,我知道,如果您更改构成该属性的基础内部属性,则该属性提供的公共“接口”不必更改,但是getters / setter方法也是如此。两者都只是应该位于对象内部的值之上的抽象层。您想在这里说什么?
亚当·帕金

8
@Adam Parkin如果客户端程序调用“ GetTaxRate()”,则您将无法删除方法“ GetTaxRate”,而不会破坏向后的可比性。但是,您可以删除'@property'而不破坏向后兼容性,因为直接访问和'@property'使用相同的语法。
AlexLordThorsen

34

这样做的目的是让您避免在实际需要时才编写getter和setter。

因此,首先要编写:

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

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

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


3
您的示例正是我所说的“使用它来修补有问题的代码”的意思……也许“有问题的”不是在此处使用的正确词,而是IMO的补丁。
olamundo

我回想起@olamundo:我经常看到变量变得@property在setter / getter中增加了一些逻辑,而没有破坏兼容性。这有意产生@property副作用,并且对于将来使用相同代码的程序员造成混乱。
洛伦佐·贝利

15

但是隐藏ab = 2不是简单的分配这一事实看起来像是麻烦的秘诀

但是,您并没有隐藏这个事实。这个事实从来没有开始过。这是python-一种高级语言;没有组装。其中很少有“简单”语句可以归结为单个CPU指令。将简单性理解为一项作业就是阅读其中不存在的东西。

当您说xb = c时,您可能应该想到的只是“无论发生什么,xb现在都应该是c”。


我同意您的意见,但我仍然认为这是“隐藏”内部工作,通常是外部用户不会期望任何方面的影响。
olamundo

5
那是真的,诺姆。但是,另一方面,OOP只是关于对象是黑匣子,除了它们所呈现的接口外,因此可能应该假设表面之下正在发生奇怪的,不可思议的事情;)
Lee B

2
ab = 2几乎不比x = 2少。如果它们的实现方式不同,那么OOP的全部重点就是隐藏实现细节。如果ab = 2将值设置为490,则这是一个讨厌的副作用,无论如何a.setB(2)不能解决。如果您隐认为AB = 2的关系,当你看到这是一个非常快速的操作,那么你应该意识到你是在Python和已经至少一个数量级比C慢
懵懵懂懂

5

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

在plone(一个相当大的CMS)中,您曾经有document.setTitle(),它执行很多操作,例如存储值,再次为其编制索引等等。只做document.title ='something'更好。您知道幕后发生了很多事情。


3

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

考虑到您有一个在应用程序中广泛使用的Foo类。现在,这个应用程序已经变得很大,可以说这是一个非常流行的Web应用程序。

您确定Foo造成了瓶颈。也许可以向Foo添加一些缓存以加快速度。使用属性可以使您做到这一点,而无需在Foo之外更改任何代码或测试。

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

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

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

经验丰富的Python程序员对以外的期望并不a.b=2a.b==2,但是他们知道那可能并非如此。在班级内部发生的事情是它自己的事。


2

这是我的一个老例子。我包装了一个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

1

getter和setter对于许多目的都是必需的,它们非常有用,因为它们对于代码是透明的。将对象Something属性设置为height,可以将值指定为Something.height = 10,但是如果height具有getter和setter方法,则在分配该值时,您可以在过程中做很多事情,例如验证min或max。值,例如因为高度改变而触发事件,根据新的高度值自动设置其他值,所有这些都可能在分配Something.height值时发生。请记住,您无需在代码中调用它们,它们在您读取或写入属性值时会自动执行。在某种程度上,它们类似于事件过程,当属性X更改值时以及读取属性X值时。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.