【问题标题】:Real world example about how to use property feature in python?关于如何在 python 中使用属性功能的真实示例?
【发布时间】:2011-09-12 08:10:12
【问题描述】:

我对如何在 Python 中使用 @property 很感兴趣。我已经阅读了 python 文档和那里的示例,在我看来,这只是一个玩具代码:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

我不知道包装带有属性装饰器的_x 可以获得什么好处。为什么不直接实现:

class C(object):
    def __init__(self):
        self.x = None

我认为,属性功能在某些情况下可能很有用。但当?有人可以给我一些真实的例子吗?

【问题讨论】:

  • 这是我找到的关于属性装饰器[click here的最好和最干净的解释]
  • @Anubis 在您提供的链接的最后一个示例中,设置 c = Celsius(-500) 没有抛出任何 ValueError,我认为这没有达到预期的结果。
  • 同意@Anubis。它在这里正确实现:python-course.eu/python3_properties.php

标签: python oop properties python-decorators


【解决方案1】:

一个简单的用例是设置一个只读实例属性,因为你知道在 python 中以一个下划线 _x 开头的变量名通常意味着它是 private (内部使用)但有时我们希望能够读取实例属性而不是写入它,因此我们可以为此使用property

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

【讨论】:

  • 如果用户愿意,仍然可以设置c._x。 Python 实际上没有真正的私有属性。
【解决方案2】:

看看this article 非常实用。简而言之,它解释了在 Python 中您通常如何放弃显式 getter/setter 方法,因为如果您在某个阶段需要它们,您可以使用 property 进行无缝实现。

【讨论】:

    【解决方案3】:

    属性只是围绕字段的抽象,它使您可以更好地控制特定字段的操作方式和进行中间件计算。很少想到的用法是验证和事先初始化和访问限制

    @property
    def x(self):
        """I'm the 'x' property."""
        if self._x is None:
            self._x = Foo()
    
        return self._x
    

    【讨论】:

      【解决方案4】:

      我使用它的一件事是缓存存储在数据库中的查找缓慢但不变的值。这适用于您的属性需要计算或其他一些您只想按需执行的长时间操作(例如数据库检查、网络通信)的任何情况。

      class Model(object):
      
        def get_a(self):
          if not hasattr(self, "_a"):
            self._a = self.db.lookup("a")
          return self._a
      
        a = property(get_a)
      

      这是在一个 web 应用程序中,任何给定的页面视图可能只需要一个此类特定属性,但底层对象本身可能具有多个此类属性 - 在构造时将它们全部初始化将是浪费的,而属性允许我灵活,哪些属性是惰性的,哪些不是。

      【讨论】:

      • 你不能用@cached_property这个吗?
      • @adarsh - 听起来很有趣。那是哪里?
      • 我一直在使用它,但我忘记了它不是内置的,但是你可以使用它,pypi.python.org/pypi/cached-property/0.1.5
      • 有趣。我认为它是在此答案之后首次发布的,但是任何阅读此内容的人都应该改用它。
      【解决方案5】:

      其他示例包括验证/过滤设置的属性(强制它们在界限内或可接受的范围内)以及对复杂或快速变化的术语进行惰性求值。

      隐藏在属性后面的复杂计算:

      class PDB_Calculator(object):
          ...
          @property
          def protein_folding_angle(self):
              # number crunching, remote server calls, etc
              # all results in an angle set in 'some_angle'
              # It could also reference a cache, remote or otherwise,
              # that holds the latest value for this angle
              return some_angle
      
      >>> f = PDB_Calculator()
      >>> angle = f.protein_folding_angle
      >>> angle
      44.33276
      

      验证:

      class Pedometer(object)
          ...
          @property
          def stride_length(self):
              return self._stride_length
      
          @stride_length.setter
          def stride_length(self, value):
              if value > 10:
                  raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
              else:
                  self._stride_length = value
      

      【讨论】:

      • 我喜欢 PDB_Calculator 的例子——复杂的东西被抽象掉了,整个东西都工作了,用户可以享受简单!
      • 可能,从专业人士的角度来看,这些都是很好的例子。但是,作为一个菜鸟,我发现这个例子非常无效。我的坏... :(
      【解决方案6】:

      很多人一开始并没有注意到的是,您可以创建自己的属性子类。我发现这对于公开只读对象属性或您可以读写但不能删除的属性非常有用。它也是包装功能(如跟踪对对象字段的修改)的绝佳方式。

      class reader(property):
          def __init__(self, varname):
              _reader = lambda obj: getattr(obj, varname)
              super(reader, self).__init__(_reader)
      
      class accessor(property):
          def __init__(self, varname, set_validation=None):
              _reader = lambda obj: getattr(obj, varname)
              def _writer(obj, value):
                  if set_validation is not None:
                     if set_validation(value):
                        setattr(obj, varname, value)
              super(accessor, self).__init__(_reader, _writer)
      
      #example
      class MyClass(object):
         def __init__(self):
           self._attr = None
      
         attr = reader('_attr')
      

      【讨论】:

      • 我喜欢这个。我是否正确地阅读了这个阅读器是只读的,而访问器是读/写而没有删除能力的?不过,您将如何添加数据验证?我对 Python 还很陌生,但我认为可能有一种方法可以将回调添加到 attr = reader('_attr') 行或某种形式的预检查,例如 attr = if self.__isValid(value): reader('_attr')。有什么建议吗?
      • 抱歉刚刚意识到我在询问只读变量的数据验证。但显然这只适用于访问器类的设置器部分。所以将attr = reader('_attr') 更改为attr = accessor('_attr')。谢谢
      • 你是对的,如果你想要验证,那么你会添加一个函数来验证,如果无效(或任何你喜欢的行为,包括什么都不做),则向 init 引发异常。我用一种可能的模式修改了上述内容。验证器应返回 True|False 以指导集合是否发生。
      【解决方案7】:

      对您的问题的简短回答是,在您的示例中,没有任何好处。您可能应该使用不涉及属性的表单。

      属性存在的原因是,如果您的代码在未来发生变化,并且您突然需要对您的数据做更多的事情:缓存值、保护访问、查询一些外部资源......无论如何,您可以轻松地修改您的类为数据添加 getter 和 setter 而无需更改接口,因此您不必在代码中找到访问数据的任何地方并对其进行更改。

      【讨论】:

        【解决方案8】:

        是的,对于发布的原始示例,该属性的工作方式与简单地使用实例变量“x”完全相同。

        这是关于 python 属性的最好的事情。从外部看,它们的工作方式与实例变量完全一样!这允许您使用类外部的实例变量。

        这意味着您的第一个示例实际上可以使用实例变量。如果事情发生了变化,然后您决定更改您的实现并且某个属性很有用,那么该属性的接口在类外部的代码中仍然是相同的。 从实例变量到属性的更改对类外的代码没有影响。

        许多其他语言和编程课程将指示程序员永远不要公开实例变量,而应使用“getters”和“setters”来从类外部访问任何值,即使是问题中引用的简单情况.

        使用多种语言(例如 Java)的类外代码

        object.get_i()
            #and
        object.set_i(value)
        
        #in place of (with python)
        object.i
            #and 
        object.i = value
        

        在实现该类时,有许多“getter”和“setter”与您的第一个示例完全相同:复制一个简单的实例变量。这些 getter 和 setter 是必需的,因为如果类实现发生更改,则类外的所有代码都需要更改。 但是python属性允许类外的代码与实例变量相同。因此,如果添加属性或具有简单的实例变量,则无需更改类外部的代码。 因此,与大多数面向对象的语言不同,对于您的简单示例,您可以使用实例变量而不是真正不需要的“getters”和“setters”,在知识上是安全的如果您将来更改为属性,则使用您的类的代码无需更改。

        这意味着您只需要在存在复杂行为时创建属性,并且对于非常常见的简单情况,如问题中所述,只需要一个简单的实例变量,您可以只使用实例变量。

        【讨论】:

          【解决方案9】:

          与使用 setter 和 getter 相比,属性的另一个不错的特性是它们允许您继续在您的 属性,同时仍保留 setter 和 getter 将提供的任何验证、访问控制、缓存等。

          例如,如果您使用 setter setage(newage) 和 getter getage() 编写类 Person,那么要增加您必须编写的年龄:

          bob = Person('Robert', 25)
          bob.setage(bob.getage() + 1)
          

          但如果您将 age 设为属性,您可以写得更简洁:

          bob.age += 1
          

          【讨论】:

            【解决方案10】:

            通读答案和 cmets,主题似乎是答案似乎缺少一个简单但有用的示例。我在这里包含了一个非常简单的例子,它演示了 @property 装饰器的简单使用。这是一个允许用户使用各种不同单位(即in_feetin_metres)指定和获取距离测量值的类。

            class Distance(object):
                def __init__(self):
                    # This private attribute will store the distance in metres
                    # All units provided using setters will be converted before
                    # being stored
                    self._distance = 0.0
            
                @property
                def in_metres(self):
                    return self._distance
            
                @in_metres.setter
                def in_metres(self, val):
                    try:
                        self._distance = float(val)
                    except:
                        raise ValueError("The input you have provided is not recognised "
                                         "as a valid number")
            
                @property
                def in_feet(self):
                    return self._distance * 3.2808399
            
                @in_feet.setter
                def in_feet(self, val):
                    try:
                        self._distance = float(val) / 3.2808399
                    except:
                        raise ValueError("The input you have provided is not recognised "
                                         "as a valid number")
            
                @property
                def in_parsecs(self):
                    return self._distance * 3.24078e-17
            
                @in_parsecs.setter
                def in_parsecs(self, val):
                    try:
                        self._distance = float(val) / 3.24078e-17
                    except:
                        raise ValueError("The input you have provided is not recognised "
                                         "as a valid number")
            

            用法:

            >>> distance = Distance()
            >>> distance.in_metres = 1000.0
            >>> distance.in_metres
            1000.0
            >>> distance.in_feet
            3280.8399
            >>> distance.in_parsecs
            3.24078e-14
            

            【讨论】:

            • 就我个人而言,getter/setter 的最佳示例是向人们展示您以后需要进行的更改,但显然这需要更多时间。
            • 是的,这是一个完美的例子,其中一个属性是某个其他属性的派生。您希望此属性在其他更新时自动更新。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-01-15
            • 2011-03-20
            • 1970-01-01
            • 2022-01-18
            • 2021-06-22
            • 2012-12-06
            • 2019-05-17
            相关资源
            最近更新 更多