与经典的getter+setter相比,@property表示法有什么优点?在哪些特定的情况下,程序员应该选择使用其中一种而不是另一种?
属性:
class MyClass(object):
@property
def my_attr(self):
return self._my_attr
@my_attr.setter
def my_attr(self, value):
self._my_attr = value
没有属性:
class MyClass(object):
def get_my_attr(self):
return self._my_attr
def set_my_attr(self, value):
self._my_attr = value
我认为两者都有各自的地位。使用@property的一个问题是,很难在子类中使用标准的类机制扩展getter或setter的行为。问题是实际的getter/setter函数隐藏在属性中。
你可以掌握这些函数,比如
class C(object):
_p = 1
@property
def p(self):
return self._p
@p.setter
def p(self, val):
self._p = val
你可以像C.p.fset和C.p.fset一样访问getter和setter函数,但是你不能很容易地使用普通的方法继承(例如super)工具来扩展它们。在深入研究了super的复杂性之后,你确实可以这样使用super:
# Using super():
class D(C):
# Cannot use super(D,D) here to define the property
# since D is not yet defined in this scope.
@property
def p(self):
return super(D,D).p.fget(self)
@p.setter
def p(self, val):
print 'Implement extra functionality here for D'
super(D,D).p.fset(self, val)
# Using a direct reference to C
class E(C):
p = C.p
@p.setter
def p(self, val):
print 'Implement extra functionality here for E'
C.p.fset(self, val)
然而,使用super()是相当笨拙的,因为必须重新定义属性,并且必须使用稍微违反直觉的super(cls,cls)机制来获得p的未绑定副本。
简单的答案是:properties轻松获胜。总是这样。
有时需要getter和setter,但即使这样,我也会将它们“隐藏”到外部世界。在Python中有很多方法可以做到这一点(getattr, setattr, __getattribute__,等等…,但最简洁明了的是:
def set_email(self, value):
if '@' not in value:
raise Exception("This doesn't look like an email address.")
self._email = value
def get_email(self):
return self._email
email = property(get_email, set_email)
下面是一篇简短的文章,介绍Python中的getter和setter主题。
在复杂的项目中,我更喜欢使用带有显式setter函数的只读属性(或getter):
class MyClass(object):
...
@property
def my_attr(self):
...
def set_my_attr(self, value):
...
在长期存在的项目中,调试和重构比编写代码本身花费更多的时间。使用@property有几个缺点。Setter,使调试更加困难:
1) python允许为现有对象创建新属性。这使得下面的打印错误很难追踪:
my_object.my_atttr = 4.
如果你的目标是一个复杂的算法,那么你将花费相当多的时间试图找出它不收敛的原因(注意上面一行中额外的“t”)
2) setter有时可能演变成一个复杂而缓慢的方法(例如击中数据库)。对于另一个开发人员来说,很难弄清楚为什么下面的函数非常慢。他可能会花很多时间分析do_something()方法,而my_object。My_attr = 4。其实是减速的原因:
def slow_function(my_object):
my_object.my_attr = 4.
my_object.do_something()