我读了很多关于@property
的优点的文章,我真的很喜欢它们。作为一个简短的例子
class Foo:
def __init__(self):
self._foo_value = 'foo_value'
@property
def foo_value(self):
return self._foo_value
my_foo = Foo()
print(my_foo.foo_value)
然而,假设我知道我不想做花哨的事情,只得到Foo.foo_value
的值,我可以通过忽略@property
来用更少的代码实现相同的行为,例如
class Foo:
def __init__(self):
self.foo_value = 'foo_value'
my_foo = Foo()
print(my_foo.foo_value)
在这种情况下仍然使用@property
有什么好处吗?
不,这里绝对没有property
的用处。property
的整点是避免样板并保持封装。
Python类定义
假设你写了一节课:class Circle:
def __init__(self, radius):
self.radius = radius
太好了。现在,有一堆客户端代码,比如:
print(f"The radius is {some_circle.radius}")
一个非Python类定义
但是,假设您希望确保半径从未设置为低于0的值。哦,不!我们该怎么办?Java等语言的理念是,您应该首先编写这样的样板getter和setter:
class Circle:
def __init__(self, radius):
self.set_radius(radius)
def get_radius(self):
return self.radius
def set_radius(self, radius):
self._radius = radius
然后,所有的客户端代码都会像这样写:
print(f"The radius is {some_circle.get_radius()}")
我们可以修改set_radius
来抛出一个适当的错误:
class Circle:
def __init__(self, radius):
self.set_radius(radius)
def get_radius(self):
return self.radius
def set_radius(self, radius):
if radius < 0:
raise ValueError("Radius must be positive")
self._radius = radius
一开始的所有样板都是值得的,因为现在我们可以在不破坏客户端代码的情况下更改控制访问内部状态的方式。
但这不是Java,这是Python
在Python中,如果和我们决定控制访问,我们可以在不破坏客户端代码的情况下重构类:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
if radius < 0:
raise ValueError("Radius must be positive")
self._radius = radius
因为让我们诚实一点。大多数时候,我们宁愿像第一个例子那样编写一个类,而不是像第二个例子那样使用先发制人的样板getter和setter,并且我们喜欢编写客户端代码,比如:
pi * circle.radius**2
而不是
pi * circle.get_radius()**2
在Python中,我们既可以有蛋糕也可以吃。
一个例外
在您的代码中,property
有一个用例:我们想让类的客户端检索值,但不设置值。就像你的例子:
>>> class Foo:
... def __init__(self):
... self._foo_value = 'foo_value'
... @property
... def foo_value(self):
... return self._foo_value
...
>>> foo = Foo()
>>> foo.foo_value
'foo_value'
>>> foo.foo_value = 'something else'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
如果以上是你想要的行为,那么property
实际上是在为目的服务。当然,这可以很容易地绕过,但它确实防止了";"意外";滥用API。而且,我们仍然可以保留更好看的foo.foo_value
,而不是更丑的foo.get_foo_value()
没有优势。如果说有什么不同的话,那就是你只是在为自己制造包袱。
其他语言(例如Java(实现";getters";以及";setters";并且具有类似CCD_ 12的变量。Python中没有private
变量;您可以使用前导_
来指示名称是私有的,或者甚至使用__
来表示";名称篡改";(但这仍然不会阻止一个坚定的用户(。
CCD_ 16有时确实有很大的用处。一个非派生的例子在我的一个包装器库的config类中。我知道0
到1
之外的值会使另一个API调用崩溃,所以如果调用失败,我会尽量防止用户陷入混乱。无论如何,我无法阻止他们这样做,但我可以尝试让API警告他们。
(补充说明;我不建议函数名称应该在CAPS中,但这实际上是API调用将设置的一些Java属性的名称,所以这是为了一致性(
@property
def CLUSTER_REGRET(self):
return self._CLUSTER_REGRET
@CLUSTER_REGRET.setter
def CLUSTER_REGRET(self, value):
if not 0 <= value <= 1:
raise ValueError("CLUSTER_REGRET must be between 0 and 1")
self._CLUSTER_REGRET = value