迭代器
- 迭代器是访问集合内元素的一种方式(惰性),和以下标的访问方式不一样、不能返回访问;
- 迭代协议:
__iter__
,__next__
,对应Iterable和Iterator; - 可迭代对象只需要实现
__iter__
,要构造迭代器还需要实现__next__
。
from collections.abc import Iterable, Iterator
a = [1, 2] # list是Iterable但不是Iterator
iter_rator = iter(a)
print (isinstance(a, Iterable))
print (isinstance(iter_rator, Iterator))
Iterable与Iterator
- 调用iter时,首先判断是否实现
__iter__
,不存在则创建默认的迭代器,利用__getitem__
进行遍历; -
__iter__
必须返回Iterator类型; - 返回迭代值是通过在Iterator类型在
__next__
实现,另外Iterator内部还需要维护一个索引值(不允许回退访问); - 要遍历某个自定义的类型,应该在其内部定义一个
__iter__
,再为它定义一个用于返回的迭代器对象(内部实现__next__
)。
示例:
from collections.abc import Iterator
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list
def __iter__(self):
return MyIterator(self.employee)
# def __getitem__(self, item):
# return self.employee[item]
class MyIterator(Iterator):
def __init__(self, employee_list):
self.iter_list = employee_list
self.index = 0
def __next__(self):
try:
word = self.iter_list[self.index]
except IndexError:
raise StopIteration
self.index += 1
return word
if __name__ == "__main__":
company = Company(["tom", "bob", "jane"])
my_itor = iter(company)
# while True:
# try:
# print (next(my_itor))
# except StopIteration:
# pass
# next(my_itor)
for item in company:
print (item)
生成器
- 只要存在yield关键字即为生成器函数,不能以一般函数的方式访问;
- 生成器对象在python编译字节码的时候产生;
- 惰性求值(不需要马上分配所有内存),每次访问都返回一个yield的值。
示例:
def fib(index):
if index <= 2:
return 1
else:
return fib(index-1) + fib(index-2)
def fib2(index):
re_list = []
n, a, b = 0, 0, 1
while n
实现细节:
# python函数的工作原理
import inspect
frame = None
def foo():
bar()
def bar():
global frame
frame = inspect.currentframe()
#
"""
python.exe调用PyEval_EvalFramEx(c函数)执行foo函数时创建一个栈帧(stack frame)
当foo调用子函数bar也会创建一个栈帧(python一切皆对象,栈帧对象,代码转化为字节码对象),
所有的栈帧都分配在堆内存上,决定了栈帧可以独立于调用者存在。
"""
import dis
print(dis.dis(foo))
foo()
print(frame.f_code.co_name) # bar
caller_frame = frame.f_back
print(caller_frame.f_code.co_name) # foo
def gen_func():
yield 1
name = "ywh"
yield 2
age = 30
return "aimei"
import dis
gen = gen_func() # 生成器对象PyGenObject是对frame的封装(PyCodeObject + PyCodeObject)
print (dis.dis(gen))
print(gen.gi_frame.f_lasti) # 可以在任何地方获取栈帧对象,暂停或恢复
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
class company:
def __getitem__(self, item):
pass
from collections import UserList # Python通过生成器实现的List(内部实现`__next__`,yield)
实例:利用生成器读取大文件
# 假设文件超大,而且只有一行(不能f.read())
def myreadlines(f, newline):
buf = "" # 缓存,处理已经读取的数据
while True:
while newline in buf: # 缓存中的数据是否包含分隔符,避免把两个字符串读取到一个buf中
pos = buf.index(newline)
yield buf[:pos] # 每次读取到分隔符的位置
buf = buf[pos + len(newline):]
chunk = f.read(4096) # 每次读取4096个字符
if not chunk: # 文件读取完毕
yield buf
break
buf += chunk
with open("input.txt") as f:
for line in myreadlines(f, "{|}"):
print(line)
生成器传值
启动生成器:
- next:从生成器获取值(目的是将值向外部传出、供其他函数调用);
- send:向生成器传传参,也可以重启生成器执行到下一个yield,返回yield的值;
- 生成器启动、发送非None值之前,必须先预激:
gen.send(None)
next(gen)
- 另外还有
close
(下次运行yield会抛出异常)、throw
(执行函数主动抛出异常)方法。
def gen_func():
html = yield "http://projectsedu.com" # 获取gen.send(html)中的参数html
print(html) # 即send传入的值
return "ywh"
gen = gen_func()
# url = gen.send(None)
url = next(gen)
html = "ywh"
gen.send(html)
gen.stop() # 生成器关闭(不能再执行yield)
gen.throw(Exception, 'message') # 抛出特定异常
yield from
chain
(合并处理可迭代对象),yield from
与yield
from itertools import chain
li = [1, 2, 3]
di = {
"ywh1": "1", "ywh2": "2"
}
for value in chain(li, di, range(5, 10)):
print(value)
def my_chain(*args, **kwargs):
for my_iterable in args:
yield from my_iterable # 对iterable解包,逐个yield出
# for value in my_iterable:
# yield value
yield from与yield的区别
def g1(iterable): # 不解包,直接yield range(10)
yield iterable
def g2(iterable): # 对range(10)解包,逐个yield出
yield from iterable
for value in g1(range(10)):
print(value)
for value in g2(range(10)):
print(value)
yield from还可以在调用方与子生成器之间建立一个双向通道(协程中调用另一个协程):
final_result = {}
def sales_sum(pro_name):
total = 0
nums = []
while True:
x = yield
print(pro_name+"销量: ", x)
if not x:
break
total += x
nums.append(x)
return total, nums
# 委托生成器(双向通道)
def middle(key):
while True:
final_result[key] = yield from sales_sum(key)
print(key + "销量统计完成!!")
def main():
data_sets = {
"ywh一月收入": [1200, 1500, 3000],
"ywh二月收入": [28, 55, 98, 108],
"ywh三月收入": [280, 560, 778, 70],
}
for key, data_set in data_sets.items():
print("start key:", key)
m = middle(key)
m.send(None) # 预激middle协程
for value in data_set:
m.send(value) # 给协程传递每一组的值
m.send(None)
print("final_result:", final_result)
if __name__ == '__main__':
main()
实现原理
# pep380
# 1. RESULT = yield from EXPR可以简化成下面这样
# 一些说明
"""
_i:子生成器,同时也是一个迭代器
_y:子生成器生产的值
_r:yield from 表达式最终的值
_s:调用方通过send()发送的值
_e:异常对象
"""
_i = iter(EXPR) # EXPR是一个可迭代对象,_i其实是子生成器;
try:
_y = next(_i) # 预激子生成器,把产出的第一个值存在_y中;
except StopIteration as _e:
_r = _e.value # 如果抛出了`StopIteration`异常,那么就将异常对象的`value`属性保存到_r,这是最简单的情况的返回值;
else:
while 1: # 尝试执行这个循环,委托生成器会阻塞;
_s = yield _y # 生产子生成器的值,等待调用方`send()`值,发送过来的值将保存在_s中;
try:
_y = _i.send(_s) # 转发_s,并且尝试向下执行;
except StopIteration as _e:
_r = _e.value # 如果子生成器抛出异常,那么就获取异常对象的`value`属性存到_r,退出循环,恢复委托生成器的运行;
break
RESULT = _r # _r就是整个yield from表达式返回的值。
"""
1. 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法;
2. 如果子生成器支持.throw()和.close()方法,但是在子生成器内部,这两个方法都会抛出异常;
3. 调用方让子生成器自己抛出异常
4. 当调用方使用next()或者.send(None)时,都要在子生成器上调用next()函数,当调用方使用.send()发送非 None 值时,才调用子生成器的.send()方法;
"""
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
总结:
- 子生成器生产的值,都是直接传给调用方的;调用方通过
send()
发送的值都是直接传递给子生成器的;如果发送的是None
,会调用子生成器的__next__()
方法,如果不是None
,会调用子生成器的send()
方法; - 子生成器退出的时候,最后的
return EXPR
,会触发一个StopIteration(EXPR)
异常; -
yield from
表达式的值,是子生成器终止时,传递给StopIteration
异常的第一个参数; - 如果调用的时候出现
StopIteration
异常,委托生成器会恢复运行,同时其他的异常会向上 "冒泡"; - 传入委托生成器的异常里,除了
GeneratorExit
之外,其他的所有异常全部传递给子生成器的throw()
方法;如果调用throw()
的时候出现了StopIteration
异常,那么就恢复委托生成器的运行,其他的异常全部向上“冒泡”; - 如果在委托生成器上调用.close()或传入
GeneratorExit
异常,会调用子生成器的close()
方法,没有的话就不调用。如果在调用close()
的时候抛出了异常,那么就向上 "冒泡",否则的话委托生成器会抛出GeneratorExit
异常。