在我看来,除了一些语法糖之外,property()没有任何好处。
当然,能够编写a.b=2
而不是a.setB(2)
很好,但隐藏了a.b=2
不是一个简单的赋值这一事实,似乎会导致麻烦。可能会发生一些意想不到的结果,例如a.b=2
实际上会使a.b
变成1
,或者引发异常,或者出现性能问题,或者只是让人感到困惑。
你能给我举个好用的具体例子吗?(将其用于修补有问题的代码不算)
在我看来,除了一些语法糖之外,property()没有任何好处。
当然,能够编写a.b=2
而不是a.setB(2)
很好,但隐藏了a.b=2
不是一个简单的赋值这一事实,似乎会导致麻烦。可能会发生一些意想不到的结果,例如a.b=2
实际上会使a.b
变成1
,或者引发异常,或者出现性能问题,或者只是让人感到困惑。
你能给我举个好用的具体例子吗?(将其用于修补有问题的代码不算)
在依赖于getter和setter的语言中,比如Java,它们不应该也不会执行除其名称所表示外的任何操作。如果x.getB()
返回当前逻辑属性b的值之外的任何内容,或者x.setB(2)
做除了使x.getB()
返回2
外其他什么工作都是惊人的。
然而,没有关于这种期望行为的语言强制的保证,即编译器对以get
或set
开头的方法的实现体施加的约束:相反,这取决于常识、社交习惯、"风格指南"和测试。
在具有属性(一组包括但不限于Python的语言)的语言中,x.b
访问和x.b = 2
等赋值的行为与例如Java中的getter和setter方法完全相同:相同的期望,相同的缺乏语言强制保证。
属性的第一个优势是语法和可读性。例如,必须编写:
x.setB(x.getB() + 1)
不使用显而易见的方法
x.b += 1
在支持属性的语言中,强制用户通过复杂的模板代码去获取类的属性是没有任何好处的,这样做只会影响代码可读性而不会带来任何益处。x
类的一个普通“存储”属性,而不是一个通过计算获取和设置的“逻辑”属性。getThis
、setThat
,也不要通过属性使用它们。__add__
改变self和/或执行与加法毫不相关的操作取决于编写者的礼貌、纪律和常识 - 这并不意味着当其被精心而明智地使用时运算符重载是一件坏事(尽管Java的设计者不同意,他们故意将它留出了_他们的_语言!)。 - Alex Martelli这个想法是让你在真正需要它们之前避免编写getter和setter。
因此,首先你要编写:
class MyClass(object):
def __init__(self):
self.myval = 4
显然,您现在可以编写myobj.myval = 5
。
但是以后,您决定需要一个setter,因为您想同时进行一些巧妙的操作。但是您不想更改使用类的所有代码-因此,您将setter包装在@property
装饰器中,所有内容都可以正常工作。
@property
以在setter/getter中添加一些逻辑而不破坏兼容性。这故意给@property
带来了副作用,对未来的程序员在同一代码上是令人困惑的。 - Lorenzo Bellia.b=2
中获得太多信息,除了 a.b==2
,但他们知道即使这也可能不是真的。类内部发生的事情是它自己的事情。这是我以前的一个例子。我封装了一个C库,其中有像“void dt_setcharge(int atom_handle, int new_charge)”和“int dt_getcharge(int atom_handle)”这样的函数。我想在Python级别上执行“atom.charge = atom.charge + 1”。
“property”装饰器使得这变得容易。类似于:
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)
十年前我写这个包时,我必须使用__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
在重构中尝试使用委托替换继承时,这将非常有用。以下是一个玩具示例。 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__'
之前的代码视为库。
__getattr__
/__setattr__
,这些远先于属性),“Java人”(包括C++!-)几乎没有其他办法来保护封装。 - Alex Martelli