类元编程是指在运行时创建或定制类的技艺,在 Python 中,类是一等对象,因此任何时候都可以使用函数新建类,无需使用 class 关键字。类装饰器也是函数,不公审查,修改甚至可以把被装饰类替换成其它类。最后,元类是类元编程最高级的工具,使用元类可以创建具有某种特质的全新类种,例如我们见过的抽象基类
标准库的一个类工厂函数 -- collections.namedtuple。我们把一个类名和几个属性名传给这个函数,它会创建一个 tuple 的子类,其中元素通过名称获取,还为调试提供了友好的字符串表示(__repr__)
我们上一章使用特性工厂函数编程模式避免重复写读值和设值方法,这里继续,把 quantity 特性工厂函数重构为 Quantity 描述符类
实现了 __get__, __set__ 或 __delete__ 方法的类是描述符。描述符的用法是,创建一个实例,作为另一个类的属性
我们将定义一个 Quantity 描述符,LineItem 会用到两个 Quantity 实例,一个管理 weight 属性,一个管理 price 属性。
Quantity 实例是 LineItem 类的属性。
Python 中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调用的属性。除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性。
除了特性,Python 还提供了丰富的 API,用于控制属性的访问权限,以及实现动态属性。使用点号访问属性时,Python 会调用特殊的方法(如 __getattr__ 和 __setattr__)计算属性。用户自定义的类可以通过 __getattr__ 方法实现 “虚拟属性”,当访问不存在的属性时,即时计算属性值
动态创建属性是一种元变成,框架作者经常这么做,在 Python 中,这种技术很简单,任何人都可以使用,甚至在日常数据转换任务都能用到
我们编写个脚本下载 OSCON 数据源,这是一份 JSON 数据,我们后面来解析它:
为了高效处理网络 I/O,需要使用并发,因为网络有很高的延时,为了不浪费 CPU 周期去等待,最好在收到网络相应之前做些其他的事
我们首先看依次从网络下载的代码:
字典中 to yield 表示产出和让步,对于 Python 生成器中的 yield 来说,这是成立的,yield item 这行代码会产生一个值,提供给 next(...) 调用方,此外,还会做出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值再调用 next()。调用方会从生成器中拉取值
语法上来说,协程和生成器类似,都是定义体中包含 yield 关键字的函数,可是,在协程中,yield 通常出现在表达式的右边,可以产出值,也可以不产出 -- 如果 yield 关键字后面没有表达式,那么生成器产出 None,协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是 next(...) 函数。通常调用方会把值推送给协程
yield 关键字甚至还可以不接收或传出数据,不过数据如何流动,yield 都是一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其他的协程
从根本上把 yield 视作控制流程的方式,这样就好理解协程了
本书前面介绍生成器函数作用不大,但是进行一系列功能改进后,得到了 Python 协程。了解 Python 协程有助于理解各个阶段的改进的功能和复杂度
本章覆盖以下话题:
在 Python 2.5 中实现了 yield 关键字可以在表达式中使用,并在生成器 API 中增加了 .send(value) 方法。生成器的调用可以使用 .send(...) 方法发送数据,发送的数据会称为生成器函数中 yield 表达式的值,因此生成器可以作为协程使用,协程指的是一个过程,这个过程与调用方协作,产出由调用方提供的值
本章讨论其他语言不常见的流程控制,用户可能会忽略这些特性:
with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并负责清理上下文。这么做能避免错误并减少样板代码,因此 API 更安全,更易于使用。除了自动关闭文件之外,with 块还有很多用途
else 子句和 with 没关系,不过这两个都内容比较短,所以放到了一个逻辑
else 子句不仅能在 if 语句中使用,还能在 for,while,try 语句中使用
else 子句行为如下:
for: 仅当 for 循环运行完毕时(即 for 循环没有被 break 语句终止)才运行 else
try: 仅当 try 块中没有异常时候才运行 else 块,else 子句抛出的异常不会由前面的 except 子句处理
在所有情况下,如果异常或者 return, break 或 continue 语句导致控制权跳到了复合语句之外,else 也会被跳过
for 循环用 else 如下:
所有生成器都是迭代器,因为生成器完全实现了迭代器接口,不过迭代器一般用于从集合取出元素,生成器用于 “凭空” 创造元素。斐波那契数列例子可以很好的说明两者区别:斐波那契数列中的数有无穷个,在一个集合里放不下。
在 Python 3 中,生成器有广泛用途。现在即使是内置的 range() 函数也要返回一个类似生成器的对象,而以前返回完整列表。如果一定让 range() 函数返回列表,必须明确指明(例如,list(range(100)))。
在 Python 中,所有集合都能迭代。在 Python 内部,迭代器用于支持:
* 拆包本章探讨以下话题:
我们创建一个类,并向它传入一些包含文本的字符串,然后可以逐个单词迭代,第 1 版要实现序列协议,这个类的对象可以迭代,因为所有序列都可以迭代 -- 这一点前面已经说过,现在说明真正的原因
下面展示了一个可以通过索引从文本提取单词的类:
我们本章会讨论:
在某些圈子里,运算符重载名声不太好,因为总被滥用.Python 加了一些限制,做好了灵活性,可用性和安全性的平衡
- (__neg__) 一元取负运算符,如果 x 是 -2, -x == 2
+ (__pos__) 一元取正运算符,通常 x == +x,但也有一些例外
~ (__invert__) 对整数按位去饭,定义 ~x == -(x + 1),如果 x 是 2, ~x == -3
支持一元操作符只需要实现相应的特殊方法,这些方法只有一个 self 参数,然后使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是不能修改 self
对于 - 和 + 来说,结果可能是与 self 属于同一类的实例,多数的时候, + 最好返回 self 的副本。abs(...) 的结果应该是一个标量,但是对于 ~ 来说,很难说明什么结果是合理的,因为可能处理的不是整数,例如 ORM 中,SQL WHERE 子句应该返回反集
© kaka 2016
Powered by Pelican