在我看来,除了一点语法糖外,property()并没有什么用。
当然,能够编写a.b=2
而不是很好a.setB(2)
,但是隐藏ab = 2不是简单赋值的事实看起来像是麻烦的秘诀,或者是因为可能发生某些意外结果,例如a.b=2
实际上导致a.b
成为1
。或引发异常。还是性能问题。或只是令人困惑。
您能给我一个具体示例以说明用法吗?(用它来修补有问题的代码不计算在内;-)
在我看来,除了一点语法糖外,property()并没有什么用。
当然,能够编写a.b=2
而不是很好a.setB(2)
,但是隐藏ab = 2不是简单赋值的事实看起来像是麻烦的秘诀,或者是因为可能发生某些意外结果,例如a.b=2
实际上导致a.b
成为1
。或引发异常。还是性能问题。或只是令人困惑。
您能给我一个具体示例以说明用法吗?(用它来修补有问题的代码不计算在内;-)
__getattr__
/ __setattr__
,它们早于属性),没有太多“ Java专家”(在任何这些语言中,包括C ++!-)可以做,以保留封装。
Answers:
在像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还是通过属性使用,都无济于事。如果您有一些具有明显可观察到的副作用的方法,请不要将其命名为getThis
,setThat
也不要通过属性使用它们。
该属性“隐藏实现”的抱怨是没有道理的全资:大部分所有的面向对象是有关实现信息隐藏-制造类负责提出一个逻辑接口与外部世界和内部实现它,因为它最能。就像属性一样,getter和setter是实现此目标的工具。属性只是做得更好(使用支持它们的语言;-)。
__add__
更改自我和/或做与加法没有任何远程关系的事情取决于编码员的礼貌,纪律和常识-这并不意味着操作员重载在有礼貌和明智地使用时是一件坏事(尽管Java的设计师不同意,因为他们故意留出来的自己的语言- !)。
这样做的目的是让您避免在实际需要时才编写getter和setter。
因此,首先要编写:
class MyClass(object):
def __init__(self):
self.myval = 4
显然您现在可以写了myobj.myval = 5
。
但是后来,您决定确实需要二传手,因为您想同时做一些聪明的事情。但是,您不需要更改所有使用您的类的代码-因此您可以将setter包装在@property
装饰器中,并且一切都可以正常进行。
@property
在setter / getter中增加了一些逻辑,而没有破坏兼容性。这有意产生@property
副作用,并且对于将来使用相同代码的程序员造成混乱。
但是隐藏ab = 2不是简单的分配这一事实看起来像是麻烦的秘诀
但是,您并没有隐藏这个事实。这个事实从来没有开始过。这是python-一种高级语言;没有组装。其中很少有“简单”语句可以归结为单个CPU指令。将简单性理解为一项作业就是阅读其中不存在的东西。
当您说xb = c时,您可能应该想到的只是“无论发生什么,xb现在都应该是c”。
您是正确的,它只是语法糖。根据您对有问题的代码的定义,可能没有很好的用途。
考虑到您有一个在应用程序中广泛使用的Foo类。现在,这个应用程序已经变得很大,可以说这是一个非常流行的Web应用程序。
您确定Foo造成了瓶颈。也许可以向Foo添加一些缓存以加快速度。使用属性可以使您做到这一点,而无需在Foo之外更改任何代码或测试。
是的,这当然是有问题的代码,但是您节省了很多$$来快速修复它。
如果Foo在您拥有成百上千个用户的库中怎么办?好了,您不必告诉他们在升级到最新版本的Foo时必须进行昂贵的重构。
发行说明有一个关于Foo的lineitem,而不是一个段落移植指南。
经验丰富的Python程序员对以外的期望并不a.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”。
“属性”装饰器使这变得容易。就像是:
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
getter和setter对于许多目的都是必需的,它们非常有用,因为它们对于代码是透明的。将对象Something属性设置为height,可以将值指定为Something.height = 10,但是如果height具有getter和setter方法,则在分配该值时,您可以在过程中做很多事情,例如验证min或max。值,例如因为高度改变而触发事件,根据新的高度值自动设置其他值,所有这些都可能在分配Something.height值时发生。请记住,您无需在代码中调用它们,它们在您读取或写入属性值时会自动执行。在某种程度上,它们类似于事件过程,当属性X更改值时以及读取属性X值时。