148

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

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

4

10 回答 10

94

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

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

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
于 2011-06-10T09:06:20.380 回答
81

一个简单的用例是设置只读实例属性,正如您所知,_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
于 2011-06-10T08:55:58.647 回答
22

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

于 2011-06-10T08:58:22.467 回答
16

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

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

于 2011-06-10T09:05:32.663 回答
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
于 2017-02-17T14:33:54.603 回答
7

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

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

    return self._x
于 2011-06-10T08:59:05.760 回答
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”与您的第一个示例完全相同:复制一个简单的实例变量。这些 getter 和 setter 是必需的,因为如果类实现发生更改,则类外的所有代码都需要更改。但是python属性允许类外的代码与实例变量相同。因此,如果您添加属性或具有简单的实例变量,则无需更改类外部的代码。因此,与大多数面向对象的语言不同,对于您的简单示例,您可以使用实例变量而不是真正不需要的“getters”和“setters”,确保如果将来更改为属性,代码使用你的班级不需要改变。

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

于 2015-12-31T23:14:55.917 回答
6

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

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

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

但如果你创建了age一个属性,你可以写得更干净:

bob.age += 1
于 2016-06-27T00:01:07.503 回答
5

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

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

于 2014-07-15T13:39:05.217 回答
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')
于 2014-01-10T19:49:52.047 回答