@属性 的优点假设我只需要获取值

Advantage of @property assuming I only need to get the value

我看了很多关于@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 有什么好处吗?

没有优势。如果有的话,你只是在给自己制造包袱。

其他语言(例如 Java)实现“getters”和“setters”并具有 private 变量之类的东西。 Python中没有private变量;您可以使用前导 _ 指示 名称是私有的,甚至可以使用 __ 来表示“名称修改”(但这仍然不会阻止确定的用户)。

有时候 @property 确实有一些很大的用处。一个非人为的例子在我的一个包装器库的配置 class 中。我知道 01 之外的值会导致另一个 API 调用崩溃,因此如果该调用失败,我会尝试阻止用户陷入混乱。无论如何,我实际上无法阻止他们这样做,但我可以尝试让 API 警告他们。

(旁注;我不建议函数名称应该大写,但这实际上是 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

No, there is absolutely no use for a property here. The entire point of property is to avoid boilerplate and maintain encapsulation.

A Pythonic class definition

Suppose you write a class:
class Circle:
    def __init__(self, radius):
        self.radius = radius

Great. Now, there's a bunch of client code out there like:

print(f"The radius is {some_circle.radius}")

An un-Pythonic class definition

Suppose, however, you want to make sure the radius is never set to a value below 0. Oh no!我们应该做什么? The philosophy in languages like Java is that you should have written boilerplate getters and setters like this in the first place:

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

Then, all the client code would have been written like:

print(f"The radius is {some_circle.get_radius()}")

And we can just modify set_radius to throw an appropriate error:

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

All that boilerplate at the beginning was worth it because now we can make changes to the way we control access to our internal state without breaking client code.

But this isn't Java, this is Python

In Python, if and when we decide to control access, we can refactor our class without breaking client code!:

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

Because let's be honest. Most of the time we'd rather write a class like the first example, not the second example with the pre-emptive boilerplate getters and setters, and we like to write client code like:

pi * circle.radius**2

instead of

pi * circle.get_radius()**2

In Python, we can have our cake and eat it too.

An exception

There is one use case for a property like in your code: we want to let the clients of our class retrieve a value but not set the value. So just like your example:

>>> 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

If he above is the behavior you want, the the property is actually serving a purpose. Of course, this can be easily circumvented, but it does prevent "accidental" misuse of the API. And, we still get to keep the nicer-looking foo.foo_value instead of the uglier foo.get_foo_value()