可迭代对象、迭代器和生成器

迭代器模式:惰性获取数据项的方式。

所有生成器都是迭代器,因为生成器完全实现了迭代器接口。在 Python社区中,大多数时候都把迭代器和生成器视作同一概念。

生成器应用广泛,即使是内置的range()也返回类似生成器对象,如果一定要返回列表,必须明确指明(例如,list(range(100)))。

序列可以迭代的原因:iter函数。

内置的iter函数有以下作用。

  1. 检查对象是否实现了__iter__方法,如果实现了即调用,获取一个迭代器。
  2. 如果没有实现__iter__方法,但实现了__getitem__方法。Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
  3. 失败,抛出TypeError异常。

任何 Python 序列都可迭代的原因是,它们都实现了__getitem__ 方法。其实,标准的序列也都实现了 __iter__方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处理,是为了向后兼容,而未来可能不会再这么做(不过,写作本书时还未弃用)。

检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常。

实现标准的可迭代协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)

def __iter__(self):
return SentenceIterator(self.words)

class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0

def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word

def __iter__(self):
return self

Python函数中有yield生成器函数,返回生成器。

生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。

标准库中的生成器函数

按用途可分为以下几类:

  • 过滤
  • 映射
  • 合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> def vowel(c):
... return c.lower() in 'aeiou'
...
>>> list(filter(vowel, 'Aardvark'))
['A', 'a', 'a']
>>> import itertools
>>> list(itertools.filterfalse(vowel, 'Aardvark'))
['r', 'd', 'v', 'r', 'k']
>>> list(itertools.dropwhile(vowel, 'Aardvark'))
['r', 'd', 'v', 'a', 'r', 'k']
>>> list(itertools.takewhile(vowel, 'Aardvark'))
['A', 'a']
>>> list(itertools.compress('Aardvark', (1,0,1,1,0,1)))
['A', 'r', 'd', 'a']
>>> list(itertools.islice('Aardvark', 4))
['A', 'a', 'r', 'd']
>>> list(itertools.islice('Aardvark', 4, 7))
['v', 'a', 'r']
>>> list(itertools.islice('Aardvark', 1, 7, 2))
['a', 'd', 'a']

yield from 语句。这个语句的作用就是把不同的生成器结合在一起使用。

上下文管理器和 else 块

with open 创建文件不创建路径。

仅当 try 块中没有异常抛出时才运行 else 块。

1
2
3
4
5
6
try:
dangerous_call()
except OSError:
log('OSError...')
else:
after_call()

with语句的目的是简化try/finally模式。

与函数和模块不同,with 块没有定义新的作用域。

多线程与协程

标准库中所有执行阻塞型 I/O 操作的函数,在等待操作系统返回结果时都会释放 GIL。这意味着在 Python 语言这个层次上可以使用多线程,而 I/O 密集型 Python 程序能从中受益:一个 Python 线程等待网络响应时,阻塞型 I/O 函数会释放 GIL,再运行一个线程。

Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn’t necessarily mean they’ll ever both be running at the same instant. For example, multitaskingon a single-core machine.

Parallelism is when tasks literally run at the same time, e.g., on a multicore processor.

异步库依赖于低层线程(直至内核级线程),但是这些库的用户无需创建线程,也无需知道用到了基础设施中的低层线程。在应用中,我们只需确保没有阻塞的代码,事件循环会在背后处理并发。异步系统能避免用户级线程的开销,这是它能比多线程系统管理更多并发连接的主要原因。

并发:充分运用CPU资源,主要是针对线程。

Coroutines

1
2
3
4
5
6
7
8
9
10
11
12
13
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)

async def main():
print(f"started at {time.strftime('%X')}")

await say_after(1, 'hello')
await say_after(2, 'world')

print(f"finished at {time.strftime('%X')}")

asyncio.run(main())
asyncio.run(coro,*,debug=False)

当前线程存在一个事件循环时,该函数不能调用。

该函数调用时新建一个事件循环并在结束时关闭循环。它应该被用作异步程序的主入口并且理想情况下,应该只调用一次。

Task Object

class asyncio.Task(coro,*,loop=None)

Task用于在事件循环中运行协程。当一个协程等待一个Future时,Task中断协程的执行并等待Future完成。当Future完成,协程恢复。

事件循环使用竞争调度:一个事件循环同一时间只运行一个任务。然而,当一任务等待Future的完成时,事件循环运行其他任务等。

asyncio.create_task(coro)

把协程包装成Task并调度其执行。Task在事件循环中执行,这个循环通过get_running_loop()获得,若当前线程没有事件循环,抛出RuntimeError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)

async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))

print(f"started at {time.strftime('%X')}")
# 并发执行
await task1
await task2

print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

Awaitables

There are three main types of awaitable objects: coroutines, Tasks, and Futures.

Tasks are used to schedule coroutines concurrently.

A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation.

Normally there is no need to create Future objects at the application level code.

coroutine asyncio.sleep(delay, result=None, *, loop=None)

sleep() always suspends the current task, allowing other tasks to run.

Running Tasks Concurrently

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

并发执行aws序列中的awaitable对象。

如果aws中有协程,自动当作Task调度。

If all awaitables are completed successfully, the result is an aggregate list of returned values. The order of result values corresponds to the order of awaitables in aws.

If return_exceptions is False (default), the first raised exception is immediately propagated to the task that awaits on gather(). Other awaitables in the aws sequence won’t be cancelled and will continue to run.

If return_exceptions is True, exceptions are treated the same as successful results, and aggregated in the result list.

If gather() is cancelled, all submitted awaitables (that have not completed yet) are also cancelled.

If any Task or Future from the aws sequence is cancelled, it is treated as if it raised CancelledError – the gather() call is not cancelled in this case. This is to prevent the cancellation of one submitted Task/Future to cause other Tasks/Futures to be cancelled.

元编程

使用点号访问属性时,Python解释器会调用特殊方法(__getattr____setattr__)计算属性。

特性:在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性。

使用特性验证属性

@property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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):
# 真正的值存储在私有属性 __weight 中
return self.__weight

@weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')

被装饰的读值方法有个 .setter 属性,这个属性也是装饰器;这个装饰器把读值方法和设值方法绑定在一起。

虽然property经常被当作装饰器使用,但它其实是一个类。构造方法如下:

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

老版Python这样用:weight = property(get_weight, set_weight)

特性会覆盖实例属性

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

实例属性遮盖类的数据属性,但实例属性不会遮盖类特性。

1
2
3
4
5
6
class Class: 
data = 'the class data attr'

@property
def prop(self):
return 'the prop value'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> Class.prop # 获取特性对象本身,不会运行特性读值方法
<property object at 0x1072b7408>

>>> obj.prop # 执行特性的读值方法
'the prop value'

>>> obj.prop = 'foo'
# 尝试给obj设置prop实例属性,失败(这实现了只读)
Traceback (most recent call last):
...
AttributeError: can't set attribute

>>> obj.__dict__['prop'] = 'foo'
# 但是可以直接把 'prop' 存入 obj.__dict__
>>> vars(obj) #
{ 'data': 'bar','prop': 'foo'}

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

>>> Class.prop = 'baz'
# 覆盖 Class.prop 特性,销毁特性对象。
>>> obj.prop
# 现在,obj.prop 获取的是实例属性。
'foo'

定义特性工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def quantity(storage_name):
def qty_getter(instance):
return instance.__dict__[storage_name]

def qty_setter(instance, value):
if value > 0:
# storage_name确定特性数据存储在哪儿
instance.__dict__[storage_name] = value
else:
raise ValueError('value must be > 0')

return property(qty_getter, qty_setter)


class ListItem:
# 使用工厂函数将自定义特性weight定义为类属性
weight = quantity('weight')
price = quantity('price')

def __init__(self, description, weight, price):
self.description = description
# 这里使用特性设值方法
self.weight = weight
self.price = price

影响属性处理方式的特殊属性

  • __class__对象所属类的引用,Python的__getattr__只在类中寻找,而不在实例中寻找。__getattr__只处理不存在的属性名。
  • __dict__存储对象或类的可写属性。

特殊方法

  • __delattr__(self, name) 只要使用 del 语句删除属性,就会调用这个方法。
  • __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__方法。
  • __setattr__(self, name, value) 点号和 setattr 内置函数会触发这个方法。

属性描述符

描述符是对多个属性运行相同存取逻辑的一种方法。

描述符是实现了特定协议的类,这个协议包括__get____set____delete__。其实,我们在真实的代码中见到的大多数描述符只实现了 __get____set__ 方法,还有很多只实现了其中的一个。

property 类实现了完整的描述符协议。

特性工厂函数借助函数式编程模式避免重复编写读值方法和设值方法。解决这种问题的面向对象的方法是描述符类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Quantity: #描述符类基于协议实现
def __init__(self, storage_name):
self.storage_name = storage_name

# 尝试为托管属性赋值(weight、price)赋值时,会调用__set__
def __set__(self, instance, value):
if value > 0:
instance.__dict__[self.storage_name] = value
else:
raise ValueError('value must be > 0')

class LineItem:
weight = Quantity('weight')
price = Quantity('price')

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

类元编程

类元编程是指在运行时创建或定制类的技术。

类装饰器也是函数,不过能够审查、修改,甚至把被装饰的类替换成其他类。

导入时和运行时区别——是有效使用Python元编程的重要基础。

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class AutoStorage:
# __counter = 0

def __init__(self):
"""初始化工作在类修饰器中"""
# cls = self.__class__
# prefix = cls.__name__
# index = cls.__counter
# self.storage_name = f"_{prefix}#{index}"
# cls.__counter += 1

def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)

def __set__(self, instance, value):
setattr(instance, self.storage_name, value)


class Validated(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)

@abc.abstractmethod
def validate(self, instance, value):
"""return vaildated value or ValueError"""


class Quantity(Validated):
"""a number greater than zero"""

def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value


class NonBlank(Validated):
"""a string non-space"""

def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value


def entity(cls):
for key, attr in cls.__dict__.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = f"_{type_name}#{key}"
return cls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import model_v5 as model

@model.entity
class LineItem:
description = model.NonBlank()
weight = model.Quantity()
price = model.Quantity()

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


if __name__ == '__main__':
item1 = LineItem("something", 15, 12)
print(dir(item1)[:3])
print(LineItem.description.storage_name)
print(item1.description)
print(getattr(item1, '_NonBlank#description'))

类装饰器以较简单的方式做到以前元类去做的事情——创建类时定制类。

类装饰器有个重大缺点:只对直接依附的类有效。这意味着,被装饰的类的子类可能继承也可能不继承装饰器所做的改动,具体情况视改动的方式而定。

元类

用于构建类的类。Python中,类也是对象,因此类必然是某个类的实例。默认情况下,Python类是type类的实例。也就是说,type 是大多数内置的类和用户定义的类的元类。为了避免无限回溯,type 是其自身的实例。