【问题标题】:Advantage of @property assuming I only need to get the value@property 的优势假设我只需要获得价值
【发布时间】:2021-08-11 21:55:12
【问题描述】:

我读过很多关于@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 有什么好处吗?”,这让我很困惑至于你在问什么。不,您的情况没有任何优势,因为它不需要使用 @property 的任何“优势”
  • 其他语言实现“getter”和“setter”。在 Python 中你不需要需要它们 - 如果你需要确保在获取或设置参数时运行其他一些逻辑,@property 就在那里
  • 是的 - 我明白你的意思,我会编辑问题以免造成进一步的混乱。我的意思是我基​​本上已经阅读了很多关于@property 并使用过几次知道的内容,但是我在互联网上能够找到的所有示例都没有涵盖这个特定的用例。无论如何,你的回答回答了我的问题,所以谢谢!

标签: python class properties


【解决方案1】:

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

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

@property 有时确实有一些很棒的实用性。我的一个包装器库的配置类中有一个不人为的例子。我知道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

【讨论】:

    【解决方案2】:

    不,property 在这里绝对没有用处。 property全部意义避免样板并保持封装。

    Pythonic 类定义

    假设你写了一个类:
    class Circle:
        def __init__(self, radius):
            self.radius = radius
    

    太好了。现在,有一堆客户端代码,例如:

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

    非 Pythonic 类定义

    但是,假设您想确保半径永远不会设置为低于 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()

    【讨论】:

    • 你的代码是不是有错误``` class Circle: def __init__(self, radius): self.radius = radius ``` 不应该是self._radius = radius吧?
    • @KimMens 不,不应该。我也希望在__init__ 中调用setter,因此它相当于self.set_radius(radius)
    • @KimMens 再次这是property 的重点,我不会更改任何客户端代码,即使在内部,我所做的只是定义我的属性如果我需要,而我已经编写的代码“正常工作”
    • 所以是因为你使用了一个属性装饰器,你想写 self.radius = radius 以便它透明地调用@radius.setter
    • @KimMens 是的,并演示实际重构将如何发生
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-11
    • 1970-01-01
    • 2011-08-03
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多