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

时间:2009-10-12 13:22:07

标签: python properties

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

当然,能够编写a.b=2代替a.setB(2)很高兴,但隐藏ab = 2不是简单赋值的事实看起来像是一个麻烦的方法,或者是因为某些意外结果可能会发生,例如a.b=2实际导致a.b1。或者引发异常。或者性能问题。或者只是让人困惑。

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

8 个答案:

答案 0 :(得分:128)

在依赖于getter和setter的语言中,比如Java,除了他们所说的内容之外,他们不应该做任何事情也不应该做任何事情 - 如果x.getB()做了什么但是返回逻辑的当前值,那将是惊人的属性b,或者如果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来说甚至更少) ;-)。他们是合理的期望,作为班级作者,你可以尝试和容纳他们 - 无论你的制定者和吸气者是直接使用还是通过财产使用,都没有区别。如果您的方法具有重要的可观察副作用,请将它们命名为getThissetThat,并且不要通过属性使用它们。

属性“隐藏实现”的抱怨是完全没有道理的:大多数所有的OOP都是关于实现信息隐藏 - 让一个类负责向外界呈现逻辑接口并实现它内部尽可能最好。吸气剂和制定者,就像属性一样,是实现这一目标的工具。属性只是做得更好(用支持它们的语言; - )。

答案 1 :(得分:31)

这个想法是让你在实际需要之前避免写入getter和setter。

所以,首先你要写:

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

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

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

答案 2 :(得分:14)

  

但是隐藏了a.b = 2不是a的事实   简单的任务看起来像一个食谱   麻烦

你并没有隐瞒这个事实;这个事实从来没有开始。这是python - 一种高级语言;不装配。其中很少有“简单”的陈述归结为单CPU指令。将简单性理解为赋值是读取不存在的东西。

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

答案 3 :(得分:5)

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

在plone(一个非常大的CMS)中,你曾经有过document.setTitle(),它可以执行很多操作,例如存储值,再次索引它等等。只是做document.title ='某事'更好。你知道无论如何在幕后发生了很多事情。

答案 4 :(得分:3)

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

请考虑您的应用程序中广泛使用的类Foo。现在这个应用程序已经变得非常庞大,并且进一步说它是一个非常受欢迎的webapp。

您认为Foo正在造成瓶颈。也许可以为Foo添加一些缓存来加速它。使用属性可以让您在不更改Foo之外的任何代码或测试的情况下执行此操作。

是的,当然这是有问题的代码,但你刚刚保存了很多$$快速修复它。

如果Foo位于您拥有数百或数千名用户的库中,该怎么办?好吧,当你升级到最新版本的Foo时,你不得不告诉他们做一个昂贵的重构。

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

经验丰富的Python程序员对a.b=2以外的a.b==2并不期望太多,但他们知道这可能不是真的。课堂内发生的事情是它自己的事业。

答案 5 :(得分: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

答案 6 :(得分:0)

getter和setter需要用于许多目的,并且非常有用,因为它们对代码是透明的。将对象设置为属性高度,将值指定为Something.height = 10,但如果高度具有getter和setter,那么在分配该值时,您可以在过程中执行许多操作,例如验证最小值或最大值值,就像因为高度改变而触发事件一样,自动设置新高度值的其他值,所有这些都可能在分配Something.height值时发生。请记住,您不需要在代码中调用它们,它们会在您读取或写入属性值时自动执行。在某种程度上,当属性X改变值并且读取属性X值时,它们就像事件过程。

答案 7 :(得分:0)

当您尝试在重构中用委托替换继承时,它很有用。下面是一个玩具示例。 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,这超出了我们的预期。

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