目录
迭代器
官方文档对迭代器的解释
This style of access is clear, concise, and convenient. The use of iterators pervades and unifies Python. Behind the scenes, the
for
statement callsiter()
on the container object. The function returns an iterator object that defines the method__next__()
which accesses elements in the container one at a time. When there are no more elements,__next__()
raises aStopIteration
exception which tells thefor
loop to terminate. You can call the__next__()
method using thenext()
built-in function; this example shows how it all works:
参考Python文档tutorial中9.8 iterator的内容。for循环在容器对象上调用iter函数,iter函数返回一个迭代器对象,迭代器对象定义了__next__方法。每调用一次__next__方法,就返回容器对象上的一个元素,如果不能返回下一个元素,那么会引发一个StopIteration异常,这样就通知for循环就终止了。
s = 'iterator'
# 可迭代对象有__iter__方法返回迭代器
it = s.__iter__()
# 迭代器调用__next__方法返回下一个迭代的元素
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())
# 引发StopIteration异常
print(it.__next__())
而for循环利用iter和next内置函数来调用被迭代对象的__iter__和__next__方法。
s = 'iterator'
# iter函数调用可迭代对象__iter__方法返回迭代器
it = iter(s)
# next函数调用迭代器__next__方法返回下一个迭代的元素
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
# 引发StopIteration异常
print(next(it))
迭代器协议
Having seen the mechanics behind the iterator protocol, it is easy to add iterator behavior to your classes. Define an
__iter__()
method which returns an object with a__next__()
method. If the class defines__next__()
, then__iter__()
can just returnself
:
迭代器协议就是官方文档对迭代器的解释。迭代器必须提供一个__next__方法,__next__方法返回迭代中的下一个对象,或者没有下一个对象时,引发一个StopIteration异常。
可迭代对象就是实现了迭代器协议的对象。具体实现方法也在官方文档中给出。就是对象定义一个__iter__方法,__iter__方法返回迭代器对象,迭代器对象必须定义__next__方法。如果类定义了__next__方法,那么__iter__方法就返回self。这个等研究面向对象时再具体分析。
官方文档的例子,定义了一个Reverse类,初始化一个字符串,倒叙迭代这个字符串。比如'spam’,for循环一个一个输出字符maps。
class Rs:
def __init__(self, data):
self.data = data
self.index = len(data)
pass
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index -= 1
return self.data[self.index]
pass
rs = Rs('spam')
for i in rs:
print(i)
pass
基于迭代器协议的统一的for循环机制
我们有了while循环,我们可以用索引方式,遍历所有序列类型的对象。为什么要用for呢?因为有些遍历没法用索引方式,比如无序的集合set、字典dict。我们实现功能时,很可能创建的就是无序的对象,所以要用for。Python基于迭代器协议的统一的for循环机制就一劳永逸(希望是这样,干过开发的都知道,怎么可能一劳永逸?)的解决了这个问题。
生成器
官方文档对生成器的解释
Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the
yield
statement whenever they want to return data. Each timenext()
is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed). An example shows that generators can be trivially easy to create:
参考Python官方文档9.9 Generator的内容。生成器可以理解为自动生成了迭代器的数据类型,因此生成器就是可迭代对象。生成器有两种形式,生成器函数和生成器表达式(9.10. Generator Expressions)。
生成器函数
生成器函数,不用return返回,用yield返回而且可以返回多次。
def test():
yield 1
yield 2
yield 3
pass
a = test()
print(type(a), a)
# <class 'generator'> <generator object test at 0x0000000003B50CF0>
print(next(a))
print(next(a))
print(next(a))
# StopIteration
print(next(a))
官方文档中也给出了一个生成器函数的用法。函数实现了返回生成器,反向逐个返回字符的迭代功能 ,注意yield的用法。
def reverse(data):
for index in range(len(data) - 1, -1, -1):
yield data[index]
# rotareneg
for i in reversed('generator'):
print(i, end='')
pass
生成器表达式
Some simple generators can be coded succinctly as expressions using a syntax similar to list comprehensions but with parentheses instead of square brackets. These expressions are designed for situations where the generator is used right away by an enclosing function. Generator expressions are more compact but less versatile than full generator definitions and tend to be more memory friendly than equivalent list comprehensions.
参考Python官方文档9.10 Generator Expressions。对于一些简单的生成器,可以用类似列表解析的语法格式来编程,只需要将列表解析的[]换成()即可。这种方式可以用于做闭包,目前还没研究,后面再研究。生成器表达式更轻量,但不能做太复杂的表述。生成器表达式不像列表解析那样消耗内存。
要理解这段话,先解释三元表达式,再解释列表解析,最后解释生成器表达式。
三元表达式,以下两种写法等价。
db = 'hana'
if db == 'hana':
os = 'linux'
else:
os = 'windows'
pass
print(os)
# linux
db = 'hana'
# 这就是三元表达式
# 条件在if和else之间,等式成立为if前面的值,不成立为else后面的值
os = 'linux' if db == 'hana' else 'windows'
print(os)
# linux
列表解析,可以利用三元表达式进行列表解析。此时生成的就是一个真实的列表,如果列表元素多,那么会占用大量的内存。
# 用三元表达式进行列表解析
# 生成的就是列表,如果列表很大会占用大量内存
n = [i for i in range(10)]
print(n)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
n = [i for i in range(10) if i > 5]
print(n)
# [6, 7, 8, 9]
生成器表达式,此时得到一个生成器,不会占用大量内存。同时实现了用一个取出一个。
# 将列表解析的[]改为()就得到了生成器表达式
# 生成器表达式的值是个生成器,自带__iter__和__next__方法,用法和迭代器一样
# 但是,这里用一个返回一个,就算列表再大也不会占用太多内存
n = (i for i in range(10))
print(n)
n = (i for i in range(10) if i > 5)
print(next(n))
print(next(n))
print(next(n))
print(next(n))
# StopIteration
print(next(n))
生成器用法举例
举一个实用的例子。读取一个文件,获取所有单词逐个处理。我们可以把文件全部读进内存然后逐个处理。我们也可以用生成器读取一个单词进内存,然后处理,节约内存。
"""
Python is an easy to learn, powerful programming language. It has efficient
high-level data structures and a simple but effective approach to
object-oriented programming. Python’s elegant syntax and dynamic typing,
together with its interpreted nature, make it an ideal language for scripting
and rapid application development in many areas on most platforms.
以上就是文件file8.txt的内容
"""
f = open('file8.txt', mode='r', encoding='utf-8')
# 将标点符号,.全部替换为空格
punctuation = ',.'
white = ' ' * len(punctuation)
trans_tab = str.maketrans(punctuation, white)
# 全部读取到内存,再拆分放进列表words
data = f.readlines()
words = []
for line in data:
words.extend(line.translate(trans_tab).split())
pass
print(words)
f.close()
我们也可以不全部读进内存,使用for迭代,逐个处理。
f = open('file8.txt', mode='r', encoding='utf-8')
# 将标点符号,.全部替换为空格
punctuation = ',.'
white = ' ' * len(punctuation)
trans_tab = str.maketrans(punctuation, white)
# 用迭代的办法逐个处理
for line in f:
for word in line.translate(trans_tab).split():
print(word, end=' ')
pass
pass
f.close()
我们最佳的办法是,用迭代器表达式。用一个单词,读取一个单词,逐个处理。
f = open('file8.txt', mode='r', encoding='utf-8')
# 将标点符号,.全部替换为空格
punctuation = ',.'
white = ' ' * len(punctuation)
trans_tab = str.maketrans(punctuation, white)
# 拿到生成器
word_g = (word for line in f for word in line.translate(trans_tab).split())
# 逐个处理单词
for word in word_g:
print(word, end=' ')
f.close()
或者,我们用函数返回一个生成器,逐个单词处理,这样可能看的更清楚一点。
def get_word_g(file, punctuation='.,', encoding='utf-8'):
white = ' ' * len(punctuation)
trans_tab = str.maketrans(punctuation, white)
# 这是另一种打开文件的写法,好处是不用关闭文件,自动关闭
with open(file, 'r', encoding=encoding) as f:
for line in f:
for word in line.translate(trans_tab).split():
yield word
pass
pass
pass
pass
wg = get_word_g('file8.txt', punctuation='.,')
for w in wg:
print(w)
利用生成器用单线程实现生产者消费者问题模型
这个问题本身应该用多线程实现的。这里只是用来说一下yield的用法。生成器函数有yield,那么函数被调用的时候返回一个生成器,函数体并不会真正执行。只有当next调用或者被send的时候才会执行,执行到yield就又停止执行了。send的内容会被yield传入。
import time
def consumer(name):
print('i am %s, i am ready to eat' % name)
while True:
food = yield
time.sleep(0.1)
print('%s eat %s' % (name, food))
pass
pass
def producer():
c1 = consumer('Kevin')
c1.send(None)
for i in range(10):
time.sleep(0.2)
food = 'bread.%s' % i
print('producer make %s' % food)
c1.send(food)
pass
producer()
生成器只能遍历一次
生成器只能遍历一次,不仅仅是显示的for循环调用,next调用,还有不明显的max、min、sorted、sum等内置函数也会这样。所以要注意!!!
def test():
for i in range(4):
yield i
pass
pass
t1 = test()
t2 = (i for i in t1)
print(list(t1))
print(list(t2))
# 生成器只能遍历一次,一次以后就没有迭代作用了
# 所以结果是
# [0, 1, 2, 3]
# []
以内置函数sum为例,说明一下。
def test():
for i in range(4):
yield i
pass
pass
t1 = test()
t2 = (i for i in t1)
print(sum(t1))
print(list(t2))
# 生成器只能遍历一次,一次以后就没有迭代作用了
# 所以结果是
# 6
# []
再以map为例说明,reduce、filter等也要注意。如果只是用了map,map返回的是map对象,这不会触发生成器迭代操作,但是如果map返回对象被使用了,那么情况就不一样了。下面两段代码请仔细对比。
def test():
for i in range(4):
yield i
pass
pass
t1 = test()
t2 = (i for i in t1)
print(map(lambda x: x ** 2, t1))
print(list(t2))
# map只是返回map对象,生成器没有遍历,所以结果是
# <map object at 0x0000000001EBFF10>
# [0, 1, 2, 3]
def test():
for i in range(4):
yield i
pass
pass
t1 = test()
t2 = (i for i in t1)
print(list(map(lambda x: x ** 2, t1)))
print(list(t2))
# map只是返回map对象,生成器被list调用时遍历,所以结果是
# [0, 1, 4, 9]
# []