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


142

我对如何@property在Python中使用感兴趣。我阅读了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

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

谢谢。


9
这是我找到的关于属性装饰器的最佳,最
简洁的

2
在您提供的链接的最后一个示例中,@ Anubis设置c = Celsius(-500)不会引发任何ValueError,我认为这没有达到预期的结果。
Sajuuk

同意@Anubis。它在此处正确实现:python-course.eu/python3_properties.php
anon01 '18

Answers:


91

其他示例是对设置的属性进行验证/过滤(强制将它们限制或接受)以及对复杂或快速变化的术语进行惰性评估。

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

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
我喜欢PDB_Calculator示例-复杂的事物被抽象出来,整个事物都可以工作,并且用户可以享受简单性!
亚当·库基维奇

2
从专业的角度来看,这些都是很好的例子。但是,作为一个新手,我发现这个例子非常无效。我的坏事... :(
kmonsoor

77

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

6
c._x如果用户需要,仍然可以设置。Python实际上没有真正的私有属性。

20

看一下这篇文章,将其用于非常实际的用途。简而言之,它解释了在Python中通常如何放弃显式的getter / setter方法,因为如果在某个阶段需要它们,则可以使用它property来实现无缝实现。


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应用程序中,其中任何给定的页面视图可能只需要一个这样的特定属性,但是基础对象本身可能具有几个这样的属性-在构造时将它们全部初始化将是浪费的,而属性使我可以灵活地使用属性是惰性的,而不是。


1
你不能用@cached_property这个吗?
adarsh 2014年

@adarsh-听起来很有趣。哪里是?
2014年

我一直在使用它,但我忘了,这是不是一个内置的,但是你可以用这个使用它,pypi.python.org/pypi/cached-property/0.1.5
ADARSH

2
有趣。我认为它是在此答案之后首次发布的,但是阅读此书的任何人都应该改用它。
2014年

10

通读答案和评论,主题似乎是答案似乎缺少一个简单但有用的示例。我在这里包括了一个非常简单的示例,演示了@property装饰器的简单用法。该类允许用户使用各种不同的单位(即in_feet或)指定并获取距离测量值in_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

对我个人而言,最佳方法是向人们展示您以后需要进行的更改,但是显然,这需要更多时间。
dtc

7

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

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

    return self._x

6

是的,对于发布的原始示例,该属性的作用与仅具有实例变量“ 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”的作用与您的第一个示例完全相同:复制一个简单的实例变量。这些获取器和设置器是必需的,因为如果类实现更改,则该类外部的所有代码都需要更改。但是python属性允许类外的代码与实例变量相同。因此,如果添加属性或具有简单的实例变量,则无需更改类外部的代码。因此,与大多数面向对象的语言不同,对于您的简单示例,您可以使用实例变量代替真正不需要的“ getters”和“ setters”,因此请确保在将来更改为属性时,可以使用您的班级无需更改。

这意味着仅当行为复杂时才需要创建属性,对于非常普遍的简单情况(如问题中所述),仅需要一个简单的实例变量,您只需使用实例变量即可。


6

与使用setter和getters相比,属性的另一个不错的功能是,它们允许您继续在属性上使用OP =运算符(例如,+ =,-=,* =等),同时仍保留任何验证,访问控制,缓存等设置者和获取者将提供。

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

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

但是如果您age有财产,您可以写得更加简洁:

bob.age += 1

5

这个问题的简短答案是,在您的示例中,没有任何好处。您可能应该使用不包含属性的表格。

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


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

我喜欢这个。我是否能正确读取此内容,而读取器是只读的,而读取器/读写器却没有删除功能?您将如何添加数据验证?我对Python还是很陌生,但是我想可能有一种方法可以在该attr = reader('_attr')行中添加回调或类似的某种形式的预检查attr = if self.__isValid(value): reader('_attr')。有什么建议吗?
Gabe Spradlin 2014年

抱歉,我刚刚意识到我正在询问只读变量的数据验证。但这显然仅适用于访问器类的setter部分。因此更改attr = reader('_attr')attr = accessor('_attr')。谢谢
Gabe Spradlin 2014年

没错,如果您想进行验证,则可以向init添加一个函数进行验证,并在无效(或您喜欢的任何行为,包括不执行任何操作)的情况下引发Exception 。我用一种可能的模式修改了上面的内容。验证器应返回True | False,以指导是否进行设置。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.