在 Python 中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调
用的属性。除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提
下,使用存取方法(即读值方法和设值方法)修改数据属性

除了特性,Python 还提供了丰富的 API,用于控制属性的访问权限,以及实现动态属性。
使用点号访问属性时(如 obj.attr),Python 解释器会调用特殊的方法(如
__getattr__ 和 __setattr__)计算属性

使用 __new__ 方法以灵活的方式创建对象
我们通常把 __init__ 称为构造方法,这是从其他语言借鉴过来的术语。其实,用于构建
实例的是特殊方法 __new__:这是个类方法(使用特殊方式处理,因此不必使用
@classmethod 装饰器),必须返回一个实例。返回的实例会作为第一个参数(即
self)传给 __init__ 方法。因为调用 __init__ 方法时要传入实例,而且禁止返回任何
值,所以 __init__ 方法其实是“初始化方法”。真正的构造方法是 __new__。我们几乎不
需要自己编写 __new__ 方法,因为从 object 类继承的实现已经足够了。

创建可读写特性

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 @property
def weight(self):
return self.__weight @weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')

去除重复的方法是抽象。抽象特性的定义有两种
方式:使用特性工厂函数,或者使用描述符类。后者更灵活

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

不适用property装饰器的例子,经典的调用

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)

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

本节的主要观点是,obj.attr 这样的表达式不会从 obj 开始寻找 attr,而是从
obj.__class__ 开始,而且,仅当类中没有名为 attr 的特性时,Python 才会在 obj 实
例中寻找。这条规则不仅适用于特性,还适用于一整类描述符——覆盖型描述符

先寻找类属性,在寻找实例属性

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

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

使用装饰器创建 property 对象时,读值方法(有 @property 装饰器的方法)的文档字
符串作为一个整体,变成特性的文档

创建特性工厂函数

def quantity(storage_name):

    def qty_getter(instance):
return instance.__dict__[storage_name] def qty_setter(instance, value):
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError('value must be > 0') return property(qty_getter, qty_setter) class LineItem:
weight = quantity('weight')
price = quantity('price') def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price

在真实的系统中,分散在多个类中的多个字段可能要做同样的验证,此时最好把
quantity 工厂函数放在实用工具模块中,以便重复使用。最终可能要重构那个简单的工
厂函数,改成更易扩展的描述符类,然后使用专门的子类执行不同的验证。

处理属性删除操作

class BlackKnight:

    def __init__(self):
self.members = ['an arm', 'another arm', 'a leg', 'another leg']
self.phrases = ["It's but a scratch.",
"It's just a flesh wound.",
"I'm invincible!",
"All right, we'll call it a draw"] @property
def member(self):
print('next member is:')
return self.members[0] @member.deleter
def member(self):
text = 'BLACK KNIGHT (loses {})\n -- {}'
print(text.format(self.members.pop(0), self.phrases.pop(0)))

影响属性处理方式的特殊属性
后面几节中的很多函数和特殊方法,其行为受下述 3 个特殊属性的影响。
__class__
  对象所属类的引用(即 obj.__class__ 与 type(obj) 的作用相同)。Python 的某些
特殊方法,例如 __getattr__,只在对象的类中寻找,而不在实例中寻找。
__dict__
  一个映射,存储对象或类的可写属性。有 __dict__ 属性的对象,任何时候都能随意
设置新属性。如果类有 __slots__ 属性,它的实例可能没有 __dict__ 属性。参见下面
对 __slots__ 属性的说明。
__slots__
  类可以定义这个这属性,限制实例能有哪些属性。__slots__ 属性的值是一个字符
串组成的元组,指明允许有的属性。 如果 __slots__ 中没有 '__dict__',那么该类
的实例没有 __dict__ 属性,实例只允许有指定名称的属性。

dir([object])
  列出对象的大多数属性。官方文档
(https://docs.python.org/3/library/functions.html#dir)说,dir 函数的目的是交互式使用,
因此没有提供完整的属性列表,只列出一组“重要的”属性名。dir 函数能审查有或没有
__dict__ 属性的对象。dir 函数不会列出 __dict__ 属性本身,但会列出其中的
键。dir 函数也不会列出类的几个特殊属性,例如 __mro__、__bases__ 和 __name__。
如果没有指定可选的 object 参数,dir 函数会列出当前作用域中的名称。
getattr(object, name[, default])
  从 object 对象中获取 name 字符串对应的属性。获取的属性可能来自对象所属的类

或超类。如果没有指定的属性,getattr 函数抛出 AttributeError 异常,或者返回
default 参数的值(如果设定了这个参数的话)。
hasattr(object, name)
  如果 object 对象中存在指定的属性,或者能以某种方式(例如继承)通过 object
对象获取指定的属性,返回 True。文档
(https://docs.python.org/3/library/functions.html#hasattr)说道:“这个函数的实现方法是调
用 getattr(object, name) 函数,看看是否抛出 AttributeError 异常。”
setattr(object, name, value)
  把 object 对象指定属性的值设为 value,前提是 object 对象能接受那个值。这个
函数可能会创建一个新属性,或者覆盖现有的属性。
vars([object])
  返回 object 对象的 __dict__ 属性;如果实例所属的类定义了 __slots__ 属性,
实例没有 __dict__ 属性,那么 vars 函数不能处理那个实例(相反,dir 函数能处理这
样的实例)。如果没有指定参数,那么 vars() 函数的作用与 locals() 函数一样:返回
表示本地作用域的字典。

使用点号或内置的 getattr、hasattr 和 setattr 函数存取属性都会触发下述列表中相
应的特殊方法。但是,直接通过实例的 __dict__ 属性读写属性不会触发这些特殊方法
——如果需要,通常会使用这种方式跳过特殊方法。

不管是使用点号存取属性,还是使用 19.6.2 节列出的某个内置函数,都会触发下述特殊方
法中的一个。例如,obj.attr 和 getattr(obj, 'attr', 42) 都会触发
Class.__getattribute__(obj, 'attr') 方法。
__delattr__(self, name)

只要使用 del 语句删除属性,就会调用这个方法。例如,del obj.attr 语句触发
Class.__delattr__(obj, 'attr') 方法。
__dir__(self)
  把对象传给 dir 函数时调用,列出属性。例如,dir(obj) 触发
Class.__dir__(obj) 方法。
__getattr__(self, name)
  仅当获取指定的属性失败,搜索过 obj、Class 和超类之后调用。表达式
obj.no_such_attr、getattr(obj, 'no_such_attr') 和 hasattr(obj,
'no_such_attr') 可能会触发 Class.__getattr__(obj, 'no_such_attr') 方法,但
是,仅当在 obj、Class 和超类中找不到指定的属性时才会触发。
__getattribute__(self, name)
  尝试获取指定的属性时总会调用这个方法,不过,寻找的属性是特殊属性或特殊方法
时除外。点号与 getattr 和 hasattr 内置函数会触发这个方法。调用
__getattribute__ 方法且抛出 AttributeError 异常时,才会调用 __getattr__ 方
法。为了在获取 obj 实例的属性时不导致无限递归,__getattribute__ 方法的实现要
使用 super().__getattribute__(obj, name)。
__setattr__(self, name, value)

尝试设置指定的属性时总会调用这个方法。点号和 setattr 内置函数会触发这个方
法。例如,obj.attr = 42 和 setattr(obj, 'attr', 42) 都会触发
Class.__setattr__(obj, ‘attr’, 42) 方法。

流畅的python第十九章元编程学习记录的更多相关文章

  1. 流畅的python第十六章协程学习记录

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yi ...

  2. 流畅的python第十四章可迭代的对象,迭代器和生成器学习记录

    在python中,所有集合都可以迭代,在python语言内部,迭代器用于支持 for循环 构建和扩展集合类型 逐行遍历文本文件 列表推导,字典推导和集合推导 元组拆包 调用函数时,使用*拆包实参 本章 ...

  3. 流畅的python第十五章上下文管理器和else块学习记录

    with 语句和上下文管理器for.while 和 try 语句的 else 子句 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文.这么做能避免错误并减少样板代码, ...

  4. 流畅的python第十二章继承的优缺点学习记录

    子类化内置类型的缺点 多重集成和方法解析顺序 tkinter

  5. 高性能MySQL之【第十五章 备份与恢复】学习记录

      我们不打算包括的话题:      安全(访问备份,恢复数据的权限,文件是否需要加密)      备份存储在哪里,包括他们应该离源数据多远,以及如何将数据从源头移动到目的地      保留策略.审计 ...

  6. Python之路【第十九章】:Django进阶

    Django路由规则 1.基于正则的URL 在templates目录下创建index.html.detail.html文件 <!DOCTYPE html> <html lang=&q ...

  7. python 教程 第十九章、 图形界面编程

    第十九章. 图形界面编程 import Tkinter top = Tkinter.Tk() hello = Tkinter.Label(top, text='Hello World!') hello ...

  8. 第十九章 Django的ORM映射机制

    第十九章 Django的ORM映射机制 第一课 Django获取多个数据以及文件上传 1.获取多选的结果(checkbox,select/option)时: req.POST.getlist('fav ...

  9. 第十九章——使用资源调控器管理资源(1)——使用SQLServer Management Studio 配置资源调控器

    原文:第十九章--使用资源调控器管理资源(1)--使用SQLServer Management Studio 配置资源调控器 本系列包含: 1. 使用SQLServer Management Stud ...

随机推荐

  1. LeetCode解题报告—— N-Queens && Edit Distance

    1. N-Queens The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no ...

  2. 如何将json拼接在url后面当做地址栏参数?

    function param(data) { let url = '' for (var k in data) { let value = data[k] !== undefined ? data[k ...

  3. CSS Modules入门及React中实践(内附webpack4配置)

    本篇文章以整理为主,自己进行了部分修改,如有侵权,请告知 CSS Modules介绍 CSS Modules是什么东西呢?首先,让我们从官方文档入手:GitHub – css-modules/css- ...

  4. 解决ssh登录过慢问题

    1.首先打开debug,看看慢在哪里 [root@BC-NFS1 ~]# ssh -v root@192.168.102.41 -p 22 OpenSSH_6.6.1, OpenSSL 1.0.1e- ...

  5. SpringMVC完成文件上传的基本步骤

    1.修改form表单的提交方式 2.将文件存入磁盘 3.配置视图解析器 1).前端文件 --需要在form表单中添加 enctype="multipart/form-data"属性 ...

  6. React Native 0.56.1初始化项目运行出现错误(Module `AccessibilityInfo` does not exist in the Haste module map)

    当使用react-native init myApp初始化项目时,出现以下错误 出现以上错误的原因是因为0.56.1版本初始化项目就有问题,请见 https://github.com/facebook ...

  7. [已解决]#1142 - SELECT command denied to user ''@'localhost' for table 'pma_table_uiprefs'

    症状:在phpmyadmin那边打不开表,提示 #1142 - SELECT command denied to user ''@'localhost' for table 'pma_table_ui ...

  8. thinkphp下实现ajax无刷新分页

    1.前言 作为一名php程序员,我们开发网站主要就是为了客户从客户端进行体验,在这里,thinkphp框架自带的分页类是每次翻页都要刷新一下整个页面,这种翻页的用户体验显然是不太理想的,我们希望每次翻 ...

  9. 在mac上无法使用Android Studio的解决方法

    随着android Studio 1.0的正式发布,估计使用的人会越来越多,并且官网上现在已经没有融合好的eclipse下载了,官方推荐下载android Studio.之前的beta版我也安装过,好 ...

  10. 【我要学python】open函数的简单用法

    open函数 1,使用方法:open('文件路径', '模式',编码方式). 2,最好使用with open as: 省去每一次都需要close()的环节 3,模式介绍: ①w 可写(如果存在,会覆盖 ...