可迭代类型: 包含方法
__iter__
迭代器类型: 包含方法__next__
1、迭代器
- 当类中定义了
__iter__
和__next__
两个方法。 __iter__
方法需要返回对象本身
。__next__
方法,返回下一个数据如果没有了,则需要抛出一个StopIiteration
的异常
1.1、创建迭代器
class IT(object):
def __init__(self):
self.count = 0
def __iter__(self):
return self
def __next__(self):
self.count += 1
if self.count == 3:
raise StopIteration()
return self.count
1.2、根据类实例化创建一个迭代器对象
# 1. 使用__next__方法获取
obj1 = IT()
v1 = obj1.__next__()
v2 = obj1.__next__()
v3 = obj1.__next__() # 抛出异常
# 2. 使用next()方法获取
v1 = next(obj1)
print(v1)
v2 = next(obj1)
print(v2)
v3 = next(obj1) # 抛出异常
print(v3)
obj2 = IT()
for item in obj2: # 首先会执行迭代器对象的__iter方法__并获取返回值, 一直去反复的执行next(对象), 把next(对象)的结果赋值给item
print(item)
# for循环内部就是通过迭代器实现
2、生成器
- 使用了
yield
的函数被称为生成器(generator)。 - 跟普通函数不同的是,生成器是一个
返回迭代器的函数
,只能用于迭代操作,更简单点理解生成器就是一个迭代器
。 - 在调用生成器运行的过程中,每次
遇到 yield 时函数会暂停并保存当前所有的运行信息
,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行
。
3、 可迭代对象
- 如果一个类中有
__iter__
方法且返回一个迭代器对象;则我们称这个类创建的对象为可迭代对象。
class Foo(object):
def __iter__(self):
return '可迭代对象'(生成器对象)
obj = Foo() # obj是可迭代对象
# 可迭代对象是可以使用for来进行循环, 在循环的内部其实就是先执行__iter__方法,获取其迭代器对象,然后再内部执行这个迭代器对象的next()功能,逐步取值
for item in obj:
pass
例:
class IT(object):
def __init__(self):
self.count = 0
def __iter__(self):
return self
def __next__(self):
self.count += 1
if self.count == 3:
raise StopIteration()
return self.count
class Foo(object): # 迭代器对象
def __iter__(self):
return IT()
obj = Foo() # obj可迭代对象
for item in obj: #循环可迭代对象时,内部先执行obj.__iter__并获取迭代器对象;不断地执行迭代器对象的next()方法。
print(item)
range内部原理:
基于迭代器自定义range:
# 基于可迭代对象&迭代器实现range
class IterRange(object):
def __init__(self, num):
self.num = num
self.counter = -1
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == self.num:
raise StopIteration()
return self.counter
class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
return IterRange(self.max_num)
obj = Xrange(100)
for item in obj:
print(item)
基于可迭代对象和生成器自定义range
# 基于可迭代对象&生成器实现自定义range
class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
counter = 0
while counter < self.max_num:
yield counter
counter += 1
obj = Xrange(100)
for item in obj:
print(item)
4、补: 生成器原理
当一个 Python 函数调用子程序(subroutine)时,这个子程序将一直持有控制权,只有当子程序结束(返回或者抛出异常)后,控制权才还给调用者。
def foo():
bar()
def bar():
pass
标准的 Python 解释器是用 C 写的。解释器用一个叫做PyEval_EvalFrameEx
的 C 函数来执行 Python 函数。它接受一个 Python 的堆栈帧(stack frame)对象,并在这个堆栈帧的上下文中执行 Python 字节码。这是 foo 的字节码:
import dis
print(dis.dis(foo))
25 0 LOAD_GLOBAL 0 (bar)
2 CALL_FUNCTION 0
4 POP_TOP
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
None
foo 函数将 bar 加载到堆栈中并调用它,然后从堆栈中弹出返回值,最后加载并返回 None.
当 PyEval_EvalFrameEx 遇到 CALL_FUNCTION 字节码的时候,它会创建一个新的 Python 堆栈帧,然后用这个新的帧作为参数递归调用 PyEval_EvalFrameEx 来执行 bar
Python 的堆栈帧是分配在堆内存中的, Python 的堆栈帧可以在它的调用之外存活
import inspect
frame = None
def foo():
bar()
def bar():
global frame
frame = inspect.currentframe()
foo()
print(frame.f_code.co_name)
caller_frame = frame.f_back
print(caller_frame.f_code.co_name)
#bar
#foo
当这项技术被用在Python生成器(generator)上
def gen_fn():
result = yield 1
print('result of yield: {}'.format(result))
result2 = yield 2
print('result of 2nd yield: {}'.format(result2))
return 'done'
当 Python 将 gen_fn 编译为字节码时,它会看到 yield 语句,然后知道 gen_fn 是个生成器函数,而不是普通函数。它会设置一个标志来记住这个事实
:
当你调用一个生成器函数时,Python 会看到生成器标志,实际上并不运行该函数,而是创建一个生成器(generator)
Python 生成器封装了一个堆栈帧和一个对生成器函数代码的引用,在这里就是对 gen_fn 函数体的引用:
gen = gen_fn()
print(gen.gi_code.co_name)
调用 gen_fn 产生的所有生成器都指向同一个代码对象,但是每个都有自己的堆栈帧。这个堆栈帧并不存在于实际的堆栈上,它在堆内存上等待着被使用
堆栈帧有个“last instruction”
指针,指向最近执行的那条指令。刚开始的时候 last instruction 指针是 -1,意味着生成器尚未开始:
print(gen.gi_frame.f_lasti)
# -1
# 当我们调用 send 时,生成器达到第一个 yield 处然后暂停执行。send 的返回值是 1,这是因为 gen 把 1 传给了 yield 表达式:
print(gen.send(None))
# 1
print(gen.gi_frame.f_lasti)
#2
print(gen.send("hello"))
#result of yield: hello
# 2