关于如何在python中使用属性功能的真实示例?

时间:2011-06-10 08:48:36

标签: python oop properties python-decorators

我对如何在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

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

感谢。

10 个答案:

答案 0 :(得分:87)

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

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

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

答案 1 :(得分:74)

一个简单的用例是设置一个只读实例属性,因为你知道在python中引导一个带有一个下划线_x的变量名通常意味着它是私有(内部使用)但是有时我们希望能够读取实例属性而不是编写它,以便我们可以使用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

答案 2 :(得分:20)

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

答案 3 :(得分:15)

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

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应用程序中,任何给定的页面视图可能只需要这种类型的一个特定属性,但底层对象本身可能有几个这样的属性 - 在构造中初始化它们将是浪费,属性允许我成为灵活,哪些属性是懒惰的,哪些属性不是。

答案 4 :(得分:7)

属性只是一个字段的抽象,它可以让您更好地控制特定字段的操作方式和中间件计算。我们想到的几个用法是验证和先前的初始化和访问限制

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

    return self._x

答案 5 :(得分:6)

使用setter和getter的属性的另一个不错的功能,它允许你继续使用OP =运算符(例如+ =, - =,* =等) 属性,同时仍然保留了setter和getter将提供的任何验证,访问控制,缓存等。

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

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

但是如果你把age作为一个属性,你可以写得更干净:

bob.age += 1

答案 6 :(得分:6)

通过阅读答案和评论,主题似乎是答案似乎缺少一个简单但有用的例子。我在这里添加了一个非常简单的例子,演示了@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

答案 7 :(得分:5)

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

属性存在的原因是,如果您的代码将来发生变化,并且您突然需要对数据执行更多操作:缓存值,保护访问权限,查询某些外部资源......无论如何,您可以轻松修改您的类为数据添加getter和setter而不更改界面,因此您不必在代码中的任何位置找到访问该数据的位置并更改它。

答案 8 :(得分:5)

是的,对于发布的原始示例,该属性与仅具有实例变量' x'完全相同。

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

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

许多其他语言和编程课程将指示程序员不应该公开实例变量,而是使用“getter”'和'塞特斯'对于从课外访问的任何值,甚至是问题中引用的简单案例。

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

object.get_i()
    #and
object.set_i(value)

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

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

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

答案 9 :(得分:4)

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

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