python cookbook啃书 第4章 迭代器、生成器



前言

迭代器(iterator)和生成器(generator)

参考来源:
https://zhuanlan.zhihu.com/p/341439647
https://www.bilibili.com/video/BV1ca411t7A9/?spm_id_from=333.788&vd_source=b3fa1d8c4f150330051049c95954f854

https://www.bilibili.com/video/BV1KS4y1D7Qb/?spm_id_from=333.337.search-card.all.click&vd_source=b3fa1d8c4f150330051049c95954f854

https://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p04_iterators_and_generators.html#


一、概要介绍

在这里插入图片描述
容器(container)

是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个迭代获取,可以用 in,not in 关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象),我们常用的 string、set、list、tuple、dict 都属于容器对象。尽管大多数容器都提供了某种方式获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有容器都是可迭代的。

可迭代对象(aiterable)
一个对象可以一个一个的返回它的成员。和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list 是可迭代对象,dict 也是可迭代对象。可迭代对象实现了 iter() 方法,该方法返回一个迭代器对象。

迭代器(iterater)
一个表示数据流的对象,可以用next()不断获取这个对象里的数据。是一个带状态(迭代器的状态指在执行过程中所处的位置和当前的值,通过一个变量来记录迭代器现在的状态)的对象,它能在你调用next()方法时返回容器中的下一个值,任何实现了__iter__()和__next__()方法的对象都是迭代器iter()返回迭代器自身,next()返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。迭代器与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,这正是它的优点。因为它并没有把所有元素装载到内存中,而是等到调用next()方法的时候才返回该元素(本质上 for 循环就是不断地调用迭代器的next()方法)。

生成器(generator)
普通函数用return返回一个值,还有一种函数用yield返回值,这种函数叫生成器函数。函数被调用时会返回一个生成器对象,yield和return都不是他的返回值,当对生成器对象使用next()函数时才会运行函数本体。生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅,它不需要像普通迭代器一样实现__iter__()和__next__()方法了,只需要一个yield关键字。yield就是返回一个值,并且记住这个返回的位置(相当于按了暂停键),下次迭代就从这个位置后(下一行)开始。生成器的的状态是函数运行到哪一步了,可以通过send()方法和yield关键字来控制和获取。生成器一定是迭代器(反之不成立),因此任何生成器也是一种懒加载的模式生成值。

iter() 语法形式:iter(object[, sentinel])
是Python中内置的一个函数,用于创建一个迭代器。object表示要被迭代的对象;sentinel是一个可选参数,用于指定迭代结束的标志。如果指定了sentinel参数,则每次迭代时,iter()函数会返回一个可调用对象(callable),该可调用对象在被调用时,会返回sentinel值,从而结束迭代。如果不指定sentinel参数,则iter()函数会返回一个迭代器对象,该对象可以用于遍历object中的元素。

他人的总结

  1. 可迭代对象(Iterable)是实现了__iter__()方法的对象,通过调用iter()方法可以获得一个迭代器(Iterator)。
  2. 迭代器(Iterator)是实现了__iter__()方法和__next()__方法的对象。
  3. for…in…的迭代实际是将可迭代对象转换成迭代器,再重复调用next()方法实现的。
  4. 生成器(Generator)是一个特殊的迭代器,它的实现更简单优雅。
  5. yield是生成器实现__next__()方法的关键。它作为生成器执行的暂停恢复点,可以对yield表达式进行赋值,也可以将yield表达式的值返回。

2.1 手动遍历可迭代对象 try…

不用for循环遍历一个可迭代对象中的所有元素(即手动遍历可迭代对象)。

代码如下(示例):

def manual_iter():
    with open('/etc/passwd') as f:
        try:
            while True:
                line = next(f)
                print(line, end='')
        except StopIteration:
            pass
# 使用 next() 函数并在代码中捕获 StopIteration 异常,StopIteration 用来指示迭代的结尾

# 也可以通过返回一个指定值来标记结尾,比如 None
with open('/etc/passwd') as f:
    while True:
        line = next(f, None)
        if line is None:
            break
        print(line, end='')
`try`是Python中的异常处理语句,用于尝试执行可能会出现异常的代码块,并在出现异常时捕获并处理异常。
`try`语句包括try关键字、一个代码块和一个或多个except块,其中代码块包含可能会出现异常的代码,except块用于捕获并处理异常。try语句的基本语法如下:
	try:
	    # 可能会出现异常的代码块
	except ExceptionType1:
	    # 处理 ExceptionType1 类型的异常
	except ExceptionType2:
	    # 处理 ExceptionType2 类型的异常
	else:
	    # 如果没有出现异常,执行这里的代码
	finally:
	    # 不管是否出现异常,都会执行这里的代码

2.2 iter repr

在一个包含有列表、元组或其他可迭代对象的容器上执行迭代操作。

只需要定义一个 iter() 方法,将迭代操作代理到容器内部的对象上去。

代码如下(示例):

class Node:
    def __init__(self, value):
        self._value = value
        self._children = []

    def __repr__(self):
        return 'Node({!r})'.format(self._value)

    def add_child(self, node):
        self._children.append(node)  

    def __iter__(self):
        return iter(self._children)  # __iter__() 方法只是简单的将迭代请求传递给内部的 _children 属性

# Example
if __name__ == '__main__':
    root = Node(0)
    child1 = Node(1)
    child2 = Node(2)
    root.add_child(child1)
    root.add_child(child2)
    # Outputs Node(1), Node(2)
    for ch in root:
        print(ch)
`__repr__()`是一个Python内置函数,用于返回一个对象的字符串表示形式。它通常被用于调试和日志记录中,也可以通过实现__repr__()方法来自定义对象的字符串表示形式。
当我们在交互式环境中打印一个对象时,实际上是调用了该对象的__repr__()方法。如果没有实现__repr__()方法,则会返回默认的字符串表示形式,即<ClassName object at memory_address>。

2.3 生成器

使用生成器来进行迭代

代码如下(示例):

def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

for n in frange(0, 4, 0.5):
     print(n)

list(frange(0, 1, 0.125))

一个函数中需要有一个 yield 语句即可将其转换为一个生成器,跟普通函数不同的是,生成器只能用于迭代操作。一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。 一旦生成器函数返回退出,迭代终止。

2.4 生成器较复杂

实现一个以深度优先方式遍历树形节点的生成器

代码如下(示例):

class Node:
    def __init__(self, value):
        self._value = value
        self._children = []

    def __repr__(self):
        return 'Node({!r})'.format(self._value)

    def add_child(self, node):
        self._children.append(node)

    def __iter__(self):
        return iter(self._children)  # 首先返回自己本身并迭代每一个子节点

    def depth_first(self):
        yield self     # yield当前节点本身
        for c in self:
            yield from c.depth_first()  # 对于当前节点的每个子节点,递归调用depth_first()方法,返回一个生成器对象,用于遍历该子节点的子树。通过yield from语句,将子树的生成器对象中的所有节点依次返回。

# Example
if __name__ == '__main__':
    root = Node(0)
    child1 = Node(1)
    child2 = Node(2)
    root.add_child(child1)
    root.add_child(child2)
    child1.add_child(Node(3))
    child1.add_child(Node(4))
    child2.add_child(Node(5))

    for ch in root.depth_first():
        print(ch)
    # Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)

2.5 reversed()反向迭代

使用内置的 reversed() 函数进行反向迭代

代码示例:

a = [1, 2, 3, 4]
for x in reversed(a):
	print(x)

反向迭代仅仅当对象的大小可预先确定或者对象实现了 reversed() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行;要注意的是如果可迭代对象元素很多的话,将其预先转换为一个列表要消耗大量的内存。

代码示例:

# Print a file backwards
f = open('somefile')
for line in reversed(list(f)):   # 将对象转换为一个列表
    print(line, end='')

在自定义类上实现 reversed() 方法来实现反向迭代,定义一个反向迭代器可以使得代码非常的高效, 因为它不再需要将数据填充到一个列表中然后再去反向迭代这个列表

代码示例:

class Countdown:
    def __init__(self, start):
        self.start = start

    # Forward iterator  正向迭代
    def __iter__(self):
        n = self.start
        while n > 0:
            yield n
            n -= 1

    # Reverse iterator  反向迭代
    def __reversed__(self):
        n = 1
        while n <= self.start:
            yield n
            n += 1

for rr in reversed(Countdown(30)):
    print(rr)
for rr in Countdown(30):
    print(rr)

2.6 将生成器实现为类调用外部状态 iter

定义一个生成器函数,但是它会调用某个你想暴露给用户使用的外部状态值,即使定义的生成器可以使用外部的东西比如库

可以简单的将生成器实现为一个类,然后把生成器函数放到 iter() 方法中过去

代码示例:

from collections import deque

class linehistory:
    def __init__(self, lines, histlen=3):
        self.lines = lines
        self.history = deque(maxlen=histlen)

    def __iter__(self):                              # 将生成器函数的实现放到__iter__()方法中,进行实现为一个类
        for lineno, line in enumerate(self.lines, 1):
            self.history.append((lineno, line))
            yield line

    def clear(self):
        self.history.clear()

with open('somefile.txt') as f:
    lines = linehistory(f)
    for line in lines:
        if 'python' in line:
            for lineno, hline in lines.history:
                print('{}:{}'.format(lineno, hline), end='')

如果生成器函数需要跟你的程序其他部分打交道的话(比如暴露属性值,允许通过方法调用来控制等等), 可能会导致你的代码异常的复杂。 如果是这种情况的话,可以考虑使用上面介绍的定义类的方式。 在 iter() 方法中定义你的生成器不会改变你任何的算法逻辑。 由于它是类的一部分,所以允许你定义各种属性和方法来供用户使用。

一个需要注意的小地方是,如果你在迭代操作时不使用for循环语句,那么你得先调用 iter() 函数

f = open('somefile.txt')
lines = linehistory(f)
next(lines)
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'linehistory' object is not an iterator

# Call iter() first, then start iterating
it = iter(lines)
next(it)
# 'hello world\n'
next(it)
# 'this is a test\n'

2.7 itertools.islice() 在生成器和迭代器上切片

使用 itertools.islice() 函数在迭代器和生成器上做切片操作

代码示例:

def count(n):
	while True:
		yield n
		n += 1
c = count(0)
c[10:20]
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable            # 迭代器和生成器不能使用标准的切片操作

# Now using islice()
import itertools
for x in itertools.islice(c, 10, 20):
	print(x)                                     # islice() 会消耗掉传入的迭代器中的数据,迭代器是不可逆的这个事实,如果需要之后再次访问这个迭代器的话,那你就得先将它里面的数据放入一个列表中。

2.8 itertools.dropwhile()跳过可迭代对象中部分元素

你想遍历一个可迭代对象,但是它开始的某些元素你并不感兴趣,想跳过它们。
使用itertools.dropwhile() 函数实现,给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。

假定你在读取一个开始部分是几行注释的源文件,代码示例:

with open('/etc/passwd') as f:
	for line in f:
		print(line, end='')

##
# User Database
#
# Note that this file is consulted directly only when the system is running
# in single-user mode. At other times, this information is provided by
# Open Directory.
...
##
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh

# 如果你想跳过开始部分的注释行的话,可以这样做:
from itertools import dropwhile
with open('/etc/passwd') as f:
	for line in dropwhile(lambda line: not line.startswith('#'), f):   # dropwhile()函数接受两个参数,第一个参数是一个函数,用于测试每一行是否需要被丢弃;第二个参数是一个可迭代对象,用于迭代行。
		print(line, end='')

如果你已经明确知道了要跳过的元素的序号的话,那么可以使用 itertools.islice() 来代替

from itertools import islice
items = ['a', 'b', 'c', 1, 4, 10, 15]
for x in islice(items, 3, None):    # 跳过前三个元素,None 参数指定了你要跳过前面3个元素, 如果 None 和3的位置对调,意思就是仅仅获取前三个元素恰恰相反 (这个跟切片的相反操作 [3:][:3] 原理是一样的)
	print(x)

2.9 itertools.permutations()、itertools.combinations()、itertools.combinations_with_replacement()输出集合中元素的排列

迭代遍历一个集合中元素的所有可能的排列或组合

itertools.permutations():接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成, 通过打乱集合中元素排列顺序生成一个元组

items = ['a', 'b', 'c']
from itertools import permutations
for p in permutations(items):
	print(p)
# outputs
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')

# 得到指定长度的所有排列,你可以传递一个可选的长度参数
for p in permutations(items, 2):
	print(p)
# outputs
# ('a', 'b')
# ('a', 'c')
# ('b', 'a')
# ('b', 'c')
# ('c', 'a')
# ('c', 'b')

itertools.combinations():可得到输入集合中元素的所有的组合,对于 combinations() 来讲,元素的顺序已经不重要了, 也就是说,组合 (‘a’, ‘b’) 跟 (‘b’, ‘a’) 其实是一样的(最终只会输出其中一个)。

items = ['a', 'b', 'c']
from itertools import combinations
for c in combinations(items, 3):
	print(c)
# outputs
# ('a', 'b', 'c')
for c in combinations(items, 2):
	print(c)
# outputs
('a', 'b')
('a', 'c')
('b', 'c')

在计算组合的时候,一旦元素被选取就会从候选中剔除掉(比如如果元素’a’已经被选取了,那么接下来就不会再考虑它了),而itertools.combinations_with_replacement():允许同一个元素被选择多次

items = ['a', 'b', 'c']
from itertools import combinations_with_replacement
for c in combinations_with_replacement(items, 3):
	print(c)
# outputs
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'a', 'c')
('a', 'b', 'b')
('a', 'b', 'c')
('a', 'c', 'c')
('b', 'b', 'b')
('b', 'b', 'c')
('b', 'c', 'c')
('c', 'c', 'c')

当我们碰到看上去有些复杂的迭代问题时,最好可以先去看看itertools模块。 如果这个问题很普遍,那么很有可能会在里面找到解决方案

2.10 enumerate()

enumerate() 函数:在迭代一个序列的同时跟踪正在被处理的元素索引;enumerate() 函数返回的是一个 enumerate 对象实例,它是一个迭代器,返回连续的包含一个计数和一个值的元组, 元组中的值通过在传入序列上调用 next() 返回。

my_list = ['a', 'b', 'c']
for idx, val in enumerate(my_list):
	print(idx, val)
# outputs
0 a
1 b
2 c
# 为了按传统行号输出(行号从1开始),可以传递一个开始参数:
for idx, val in enumerate(my_list, 1):
	print(idx, val)

注意:

data = [ (1, 2), (3, 4), (5, 6), (7, 8) ]

# Correct!
for n, (x, y) in enumerate(data):
    ...
# Error!
for n, x, y in enumerate(data):
    ...

2.11 zip()

zip() 函数:同时迭代多个序列,每次分别从一个序列中取一个元素。 一旦其中某个序列到底结尾,迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致。itertools.zip_longest() 函数,以长的序列结束。

xpts = [1, 5, 4, 2, 10, 7]
ypts = [101, 78, 37, 15, 62, 99]
for x, y in zip(xpts, ypts):
	print(x,y)
# outputs
1 101
5 78
4 37
2 15
10 62
7 99

# 使用zip()可以让你将它们打包并生成一个字典
headers = ['name', 'shares', 'price']
values = ['ACME', 100, 490.1]
s = dict(zip(headers,values))

zip() 会创建一个迭代器来作为结果返回。 如果你需要将结对的值存储在列表中,要使用 list() 函数。

>>> zip(a, b)
<zip object at 0x1007001b8>
>>> list(zip(a, b))
[(1, 10), (2, 11), (3, 12)]

2.12 itertools.chain()

itertools.chain():在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环;受一个或多个可迭代对象作为输入参数, 然后创建一个迭代器,依次连续的返回每个可迭代对象中的元素,比先将序列合并再迭代要高效的多。

from itertools import chain
a = [1, 2, 3, 4]
b = ['x', 'y', 'z']
for x in chain(a, b):
	print(x)
# outputs
1
2
3
4
x
y
z


# 对不同的集合中所有元素执行某些操作的时候
active_items = set()
inactive_items = set()
# Iterate over all items
for item in chain(active_items, inactive_items):
    

2.13 实现数据管道 yield from

用生成器函数实现管道机制(数据管道)迭代处理数据,比如,你有个大量的数据需要处理,但是不能将它们一次性放入内存中。

以管道方式处理数据
	指将数据在不同的处理阶段中通过管道传递,通过一系列处理步骤对数据进行转换、清洗、聚合等操作,最终得到处理后的结果。
	通常,这种处理方式是以流式处理的形式进行,即数据在流动过程中被处理,而不是一次性处理所有数据。
	以管道方式处理数据的好处包括:
	1. 灵活性:管道方式处理数据可以灵活地进行数据流的控制和处理,可以根据实际需求进行修改和调整。
	2. 可扩展性:管道可以很容易地进行扩展,可以添加或删除处理步骤,以适应不同的数据处理需求。
	3. 高效性:管道处理数据的效率高,因为数据可以在不同的处理阶段中并行处理,从而提高了数据处理的速度。
	4. 可重复性:管道可以很容易地进行重复使用,可以将处理步骤保存为一个模板并复用。
	5. 实时性:管道可以支持实时处理数据,可以及时地处理数据流并提供实时反馈。
在现代数据处理系统中,管道方式处理数据已经成为一种流行的数据处理模式。常见的数据处理工具,如Apache Beam、Apache NiFi、Apache Kafka等都支持管道方式处理数据,可以帮助用户快速构建和部署可靠、可扩展的数据处理管道。
数据管道 Data Pipeline
	指一种将数据从一个或多个来源地转移到一个或多个目标地的系统或流程。
	数据管道的主要目的是将数据从数据源中提取、转换和加载到目标位置,以供后续的数据分析、数据挖掘、报告和可视化等应用。
	1. 数据提取(Extraction):从一个或多个数据源中提取数据,并将其存储在一个中间仓库中。
	2. 数据转换(Transformation):对数据进行清洗、过滤、转换、聚合等操作,以便于后续的分析和应用。
	3. 数据加载(Loading):将处理后的数据加载到目标位置,如数据仓库、数据湖等。
	数据管道的好处包括:
	4. 数据集成:数据管道可以将分散在不同地方的数据集成到一个中心位置,以便于管理和分析。
	5. 数据清洗:数据管道可以对数据进行清洗和转换,使其符合分析和应用的要求。
	6. 自动化:使用ETL工具可以自动化数据管道的构建和维护,减少人工干预和错误。
	7. 实时性:数据管道可以实现实时或近实时的数据传输和处理,以满足实时的业务需求。
总之,数据管道是数据管理和分析的重要组成部分,可以将数据从不同的来源地转移到目标位置,以便于后续的分析和应用。

代码示例

# 假定要处理一个非常大的日志文件目录:
'''
foo/
    access-log-012007.gz
    access-log-022007.gz
    access-log-032007.gz
    ...
    access-log-012008
bar/
    access-log-092007.bz2
    ...
    access-log-022008
'''

# 假设每个日志文件包含这样的数据:
'''
124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369
61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 -
'''

# 为了处理这些文件,你可以定义一个由多个执行特定任务独立任务的简单生成器函数组成的容器。就像这样:
import os
import fnmatch
import gzip
import bz2
import re

def gen_find(filepat, top):
    '''
    Find all filenames in a directory tree that match a shell wildcard pattern
    '''
    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist, filepat):
            yield os.path.join(path,name)

def gen_opener(filenames):
    '''
    Open a sequence of filenames one at a time producing a file object.
    The file is closed immediately when proceeding to the next iteration.
    '''
    for filename in filenames:
        if filename.endswith('.gz'):
            f = gzip.open(filename, 'rt')  # 'rt':表示以文本模式读取文件内容,其中'r'表示读取模式,而't'表示文本模式。
        elif filename.endswith('.bz2'):
            f = bz2.open(filename, 'rt')
        else:
            f = open(filename, 'rt')
        yield f
        f.close()

def gen_concatenate(iterators):
    '''
    Chain a sequence of iterators together into a single sequence. 将输入序列拼接成一个很长的行序列
    '''
    for it in iterators:
        yield from it

def gen_grep(pattern, lines):
    '''
    Look for a regex pattern in a sequence of lines
    '''
    pat = re.compile(pattern) # re.compile是将正则表达式编译成正则对象的函数,其中pattern表示要编译的正则表达式字符串
    for line in lines:
        if pat.search(line):  # re.search是一个用于在字符串中查找匹配项的函数,在整个字符串中查找第一个匹配项,如果找到则返回一个匹配对象,否则返回None;re.search(pattern, string),其中,pattern表示要匹配的正则表达式,string表示要在其中查找匹配项的字符串
            yield line

# 现在可以很容易的将这些函数连起来创建一个处理管道。 比如,为了查找包含单词python的所有日志行,你可以这样做:
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
for line in pylines:
    print(line)

# 如果将来的时候想扩展管道,甚至可以在生成器表达式中包装数据;比如,下面这个版本计算出传输的字节数并计算其总和:
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != '-')
print('Total', sum(bytes))

了理解上述代码,重点是要明白 yield 语句作为数据的生产者for 循环语句作为数据的消费者。 当这些生成器被连在一起后,每个 yield 会将一个单独的数据元素传递给迭代处理管道的下一阶段。 在例子最后部分, sum() 函数是最终的程序驱动者,每次从生成器管道中提取出一个元素。

这种方式一个非常好的特点是每个生成器函数很小并且都是独立的。这样的话就很容易编写和维护它们了。

由于使用了迭代方式处理,代码运行过程中只需要很小很小的内存。

yield from <iterable>
	可以将一个嵌套的可迭代对象展开成一个迭代器,从而简化生成器函数的编写
	其中,<iterable>表示要展开的可迭代对象,可以是列表、元组、集合、字典等等。当生成器函数执行到yield from语句时,它会先暂停自己的执行,并将控制权转交给<iterable>所生成的迭代器,让它产出值。当<iterable>产出的值用完时,控制权会自动返回给生成器函数,并继续执行下面的语句。
	如果<iterable>是一个生成器函数,则可以在其中使用yield语句向调用者产出值,同时也可以接收调用者发来的值,从而实现协程的功能。
代码示例:
def sub_generator():
    yield 'a'
    yield 'b'
    yield 'c'
def main_generator():
    yield 'start'
    yield from sub_generator()
    yield 'end'
for item in main_generator():
    print(item)

2.14 yield from isinstance() ignore_types

用yield from将一个多层嵌套的序列展开成一个单层列表

from collections import Iterable

def flatten(items, ignore_types=(str, bytes)):
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):   # isinstance(x, Iterable) 检查某个元素是否是可迭代的; 
            yield from flatten(x)
        else:
            yield x

items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
    print(x)
isinstance(object, classinfo)
isinstance()是Python内置函数之一,用于判断一个对象是否为指定类或其子类的实例。isinstance()是Python内置函数之一,用于判断一个对象是否为指定类或其子类的实例。
ignore_types是一个常见的参数名,用于指定在某个操作中要忽略的数据类型列表,通常用于过滤掉一些不需要处理的数据类型
	在递归函数中,我们可以使用ignore_types参数来避免无限递归。
	在序列化和反序列化数据时,我们可以使用ignore_types参数来避免不必要的数据转换。
	在处理异常时,我们可以使用ignore_types参数来忽略一些不需要处理的异常类型。

2.15 heapq.merge()

有一系列排序序列,将它们合并(heapq.merge() )后得到一个排序序列并在上面迭代遍历。

import heapq
a = [1, 4, 7, 10]
b = [2, 5, 6, 11]
for c in heapq.merge(a, b):
	print(c)

heapq.merge 可迭代特性意味着它不会立马读取所有序列。 这就意味着你可以在非常长的序列中使用它,而不会有太大的开销。
**heapq.merge() 需要所有输入序列必须是排过序的。**特别的,它并不会预先读取所有数据到堆栈中或者预先排序,也不会对输入做任何的排序检测。 它仅仅是检查所有序列的开始部分并返回最小的那个(两个序列的开始位置),这个过程一直会持续直到所有输入序列中的元素都被遍历完。

2.16 用迭代器代替while循环 callable(可迭代)对象

while循环(需要测试条件)可以迭代处理数据,用迭代器代替while循环

一个常见的IO操作程序可能会想下面这样:

CHUNKSIZE = 8192

# while循环
def reader(s):
    while True:
        data = s.recv(CHUNKSIZE)   # recv()是Python中socket模块中的一个函数,用于从一个已连接的套接字(socket)中接收数据,其中,socket是一个已连接的套接字对象
        if data == b'':
            break
        process_data(data)

# 迭代器
def reader2(s):
    for chunk in iter(lambda: s.recv(CHUNKSIZE), b''): 
        pass
        # process_data(data)

callable是一个内置函数,用于检查一个对象是否可调用。可调用对象包括函数、方法、类、实现了__call__()方法的实例等等。如果对象是可调用的,则callable函数返回True


总结

迭代器和生成器知识,及python cookbook上的使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值