关于如何在 python 中使用属性特性的实际例子?

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

我不知道我可以得到什么好处(s)从包装的 _x充满了属性装饰。为什么不直接实现:

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

我认为,属性特性在某些情况下可能是有用的。但是什么时候呢?谁能给我一些现实生活中的例子?

73543 次浏览

一个简单的用例是设置一个只读的实例属性,因为你知道在 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

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

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

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


return self._x

我使用它的一个目的是缓存存储在数据库中的缓慢查找但不变的值。这一点可以推广到任何需要计算属性或其他长时间操作(例如,数据库检查、网络通信)的情况,而这些操作只能在需要时进行。

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

其他的例子包括集合属性的验证/过滤(强制它们处于界限内或可接受)和复杂或快速变化的术语的惰性计算。

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

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

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

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

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

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

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

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

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

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

使用多种语言(例如 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”和“ setter”,安全的知识,如果你改变到一个属性在未来,使用你的类的代码不需要改变。

这意味着你只需要在有复杂行为的情况下创建属性,对于非常常见的简单情况,就像问题中描述的那样,只需要一个简单的实例变量,你就可以使用实例变量。

相对于使用 setter 和 getters,属性的另一个很好的特性是,它们允许您继续在您的 属性,同时仍然保留 setter 和 getter 提供的任何验证、访问控制、缓存等。

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

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

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

bob.age += 1

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