在我看来,除了一点点语法糖,property()没有任何好处。
当然,能够编写a.b=2
代替a.setB(2)
很高兴,但隐藏ab = 2不是简单赋值的事实看起来像是一个麻烦的方法,或者是因为某些意外结果可能会发生,例如a.b=2
实际导致a.b
为1
。或者引发异常。或者性能问题。或者只是让人困惑。
你能给我一个具体的例子来好好利用它吗? (使用它来修补有问题的代码不计算在内; - )
答案 0 :(得分:128)
在依赖于getter和setter的语言中,比如Java,除了他们所说的内容之外,他们不应该做任何事情也不应该做任何事情 - 如果x.getB()
做了什么但是返回逻辑的当前值,那将是惊人的属性b
,或者如果x.setB(2)
做了什么,除了需要少量的内部工作才能使x.getB()
返回2
。
但是,没有语言强加的保证关于此预期行为,即编号强制约束对名称以get
或set
开头的方法体:相反,它取决于常识,社会习俗,“风格指南”和测试。
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来说甚至更少) ;-)。他们是合理的期望,作为班级作者,你可以尝试和容纳他们 - 无论你的制定者和吸气者是直接使用还是通过财产使用,都没有区别。如果您的方法具有重要的可观察副作用,请不将它们命名为getThis
,setThat
,并且不要通过属性使用它们。
属性“隐藏实现”的抱怨是完全没有道理的:大多数所有的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)
当您尝试在重构中用委托替换继承时,它很有用。下面是一个玩具示例。 Stack 是 Vector 中的一个子类。
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__'
作为库。