返回介绍

19.3 特性全解析

发布于 2024-02-05 21:59:47 字数 5111 浏览 0 评论 0 收藏 0

虽然内置的 property 经常用作装饰器,但它其实是一个类。在 Python 中,函数和类通常可以互换,因为二者都是可调用的对象,而且没有实例化对象的 new 运算符,所以调用构造方法与调用工厂函数没有区别。此外,只要能返回新的可调用对象,代替被装饰的函数,二者都可以用作装饰器。

property 构造方法的完整签名如下:

property(fget=None, fset=None, fdel=None, doc=None)

所有参数都是可选的,如果没有把函数传给某个参数,那么得到的特性对象就不允许执行相应的操作。

property 类型在 Python 2.2 中引入,但是直到 Python 2.4 才出现 @ 装饰器句法,因此有那么几年,若想定义特性,则只能把存取函数传给前两个参数。

不使用装饰器定义特性的“经典”句法如示例 19-18 所示。

示例 19-18 bulkfood_v2b.py:效果与示例 19-17 一样,只不过没使用装饰器

class LineItem:

  def __init__(self, description, weight, price):
    self.description = description
    self.weight = weight
    self.price = price

  def subtotal(self):
    return self.weight * self.price

  def get_weight(self):  ➊
    return self.__weight

  def set_weight(self, value):  ➋
    if value > 0:
      self.__weight = value
    else:
      raise ValueError('value must be > 0')

  weight = property(get_weight, set_weight)  ➌

❶ 普通的读值方法。

❷ 普通的设值方法。

❸ 构建 property 对象,然后赋值给公开的类属性。

某些情况下,这种经典形式比装饰器句法好;稍后讨论的特性工厂函数就是一例。但是,在方法众多的类定义体中使用装饰器的话,一眼就能看出哪些是读值方法,哪些是设值方法,而不用按照惯例,在方法名的前面加上 get 和 set。

类中的特性能影响实例属性的寻找方式,而一开始这种方式可能会让人觉得意外。下一节会详细说明。

19.3.1 特性会覆盖实例属性

特性都是类属性,但是特性管理的其实是实例属性的存取。

9.9 节说过,如果实例和所属的类有同名数据属性,那么实例属性会覆盖(或称遮盖)类属性——至少通过那个实例读取属性时是这样。示例 19-19 阐明了这一点。

示例 19-19 实例属性遮盖类的数据属性

>>> class Class:   # ➊
...   data = 'the class data attr'
...   @property
...   def prop(self):
...     return 'the prop value'
...
>>> obj = Class()
>>> vars(obj)  # ➋
{}
>>> obj.data  # ➌
'the class data attr'
>>> obj.data = 'bar' # ➍
>>> vars(obj)  # ➎
{'data': 'bar'}
>>> obj.data  # ➏
'bar'
>>> Class.data  # ➐
'the class data attr'

❶ 定义 Class 类,这个类有两个类属性:data 数据属性和 prop 特性。

❷ vars 函数返回 obj 的 __dict__ 属性,表明没有实例属性。

❸ 读取 obj.data,获取的是 Class.data 的值。

❹ 为 obj.data 赋值,创建一个实例属性。

❺ 审查实例,查看实例属性。

❻ 现在读取 obj.data,获取的是实例属性的值。从 obj 实例中读取属性时,实例属性 data 会遮盖类属性 data。

❼ Class.data 属性的值完好无损。

下面尝试覆盖 obj 实例的 prop 特性。接着前面的控制台会话,输入示例 19-20 中的代码。

示例 19-20 实例属性不会遮盖类特性(接续示例 19-19)

>>> Class.prop  # ➊
<property object at 0x1072b7408>
>>> obj.prop  # ➋
'the prop value'
>>> obj.prop = 'foo'  # ➌
Traceback (most recent call last):
  ...
AttributeError: can't set attribute
>>> obj.__dict__['prop'] = 'foo'  # ➍
>>> vars(obj)  # ➎
{ 'data': 'bar','prop': 'foo'}
>>> obj.prop  # ➏
'the prop value'
>>> Class.prop = 'baz'  # ➐
>>> obj.prop  # ➑
'foo'

❶ 直接从 Class 中读取 prop 特性,获取的是特性对象本身,不会运行特性的读值方法。

❷ 读取 obj.prop 会执行特性的读值方法。

❸ 尝试设置 prop 实例属性,结果失败。

❹ 但是可以直接把 'prop' 存入 obj.__dict__。

❺ 可以看到,obj 现在有两个实例属性:data 和 prop。

❻ 然而,读取 obj.prop 时仍会运行特性的读值方法。特性没被实例属性遮盖。

❼ 覆盖 Class.prop 特性,销毁特性对象。

❽ 现在,obj.prop 获取的是实例属性。Class.prop 不是特性了,因此不会再覆盖 obj.prop。

最后再举一个例子,为 Class 类新添一个特性,覆盖实例属性。示例 19-21 接续示例 19-20。

示例 19-21 新添的类特性遮盖现有的实例属性(接续示例 19-20)

>>> obj.data  # ➊
'bar'
>>> Class.data  # ➋
'the class data attr'
>>> Class.data = property(lambda self: 'the "data" prop value')  # ➌
>>> obj.data  # ➍
'the "data" prop value'
>>> del Class.data  # ➎
>>> obj.data  # ➏
'bar'

❶ obj.data 获取的是实例属性 data。

❷ Class.data 获取的是类属性 data。

❸ 使用新特性覆盖 Class.data。

❹ 现在,obj.data 被 Class.data 特性遮盖了。

❺ 删除特性。

❻ 现在恢复原样,obj.data 获取的是实例属性 data。

本节的主要观点是,obj.attr 这样的表达式不会从 obj 开始寻找 attr,而是从 obj.__class__ 开始,而且,仅当类中没有名为 attr 的特性时,Python 才会在 obj 实例中寻找。这条规则不仅适用于特性,还适用于一整类描述符——覆盖型描述符(overriding descriptor)。第 20 章会进一步讨论描述符,那时你会发现,特性其实是覆盖型描述符。

现在回到特性。各种 Python 代码单元(模块、函数、类和方法)都可以有文档字符串。下一节说明如何把文档依附到特性上。

19.3.2 特性的文档

控制台中的 help() 函数或 IDE 等工具需要显示特性的文档时,会从特性的 __doc__ 属性中提取信息。

如果使用经典调用句法,为 property 对象设置文档字符串的方法是传入 doc 参数:

weight = property(get_weight, set_weight, doc='weight in kilograms')

使用装饰器创建 property 对象时,读值方法(有 @property 装饰器的方法)的文档字符串作为一个整体,变成特性的文档。图 19-2 显示的是从示例 19-22 里的代码中生成的帮助界面。

图 19-2:在 Python 控制台中执行 help(Foo.bar)help(Foo) 命令时的截图;源码在示例 19-22 中

示例 19-22 特性的文档

class Foo:

  @property
  def bar(self):
    '''The bar attribute'''
    return self.__dict__['bar']

  @bar.setter
  def bar(self, value):
    self.__dict__['bar'] = value

至此,我们介绍了特性的重要知识。下面回过头来解决前面遇到的问题:保护 LineItem 对象的 weight 和 price 属性,只允许设为大于零的值;但是,不用手动实现两对几乎一样的读值方法和设值方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文