python中的迭代协议
1.python中的迭代协议与迭代器
迭代器是访问集合内元素的一种方式,一般用来遍历数据
迭代器是不会重复的,只会一条一条访问完数据
迭代器提供了一种惰性访问数据的方式
迭代协议__iter__
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
__slots__
是限制类能添加的属性的,详情:https://blog.csdn.net/qq_37616069/article/details/79688781
迭代器
一个指着容器的指针
继承了Iterable
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
Iterator.register(bytes_iterator)
Iterator.register(bytearray_iterator)
#Iterator.register(callable_iterator)
Iterator.register(dict_keyiterator)
Iterator.register(dict_valueiterator)
Iterator.register(dict_itemiterator)
Iterator.register(list_iterator)
Iterator.register(list_reverseiterator)
Iterator.register(range_iterator)
Iterator.register(longrange_iterator)
Iterator.register(set_iterator)
Iterator.register(str_iterator)
Iterator.register(tuple_iterator)
Iterator.register(zip_iterator)
主要还是在__next__
上,获取下一个元素,迭代器其实已经重载了iter
list实现了iter,所以是一个可迭代类型,但不是迭代器
迭代器必须实现next方法
a = []
iter_rator = iter(a)
print(isinstance(iter_rator, Iterator))
True
iter函数可以给容器分配一个迭代器(已经可以不用写了)
from _collections_abc import *
class a:
def __init__(self, ls):
self.ls = ls
def __getitem__(self, item):
return self.ls[item]
aa = a([1, 2, 3, 4, 5])
print(isinstance(aa, Iterable))
print(isinstance(aa, Iterator))
for item in aa:
print(item)
'''当用这种方式调用,会先尝试类里的__iter__方法,如果没有,会先创建默认迭代器,再调用__getitem__'''
b = iter(aa)
print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
for item in b:
print(item)
False
False
1
2
3
4
5
True
True
1
2
3
4
5
自定义迭代器:
from _collections_abc import *
class Myitor(Iterator):
def __init__(self, ls):
self.ls = ls
self.index = 0
self.len = len(ls)
def __next__(self):
if self.index == self.len:
return 'over'
w = self.ls[self.index]
print('index', self.index)
self.index += 1
return w
a = [1, 2, 3, 4]
pointer = Myitor(a)
while True:
print(next(pointer))
'''指针往后指一位'''
2.生成器函数的使用
只要函数里有yield关键字,就是生成器函数
生成器函数返回一个生成器对象
在python编译字节码时候产生
生成器函数也是对象,且实现了迭代协议,所以其实例(返回的值)可以用for循环访问
import time
def gen_func():
for i in range(5):
'''可多次yield到调用包'''
yield round(time.time())
time.sleep(1)
gen = gen_func()
for value in gen:
print(value)
1595003121
1595003122
1595003123
1595003124
1595003125
这让我们实现协程、惰性求值、延迟求值成为可能
生成器斐波那契:
def gen_func(index):
n, a, b = 0, 0, 1
while n<index:
yield b
a, b = b, a+b
n += 1
gen = gen_func(3)
for value in gen:
print(value)
3.生成器原理
python.exe是用c写的,且会用c语言函数PyEval_EvalFramEx去执行python的函数
调用函数时首先会创建一个栈帧stack frame
python中一切皆对象,栈帧也是对象,把代码变成字节码对象
import dis
def foo():
bar()
def bar():
pass
print(dis.dis(foo))
41 0 LOAD_GLOBAL 0 (bar)
2 CALL_FUNCTION 0
4 POP_TOP
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
None
当调用子函数,又会创建一个栈帧,在新栈帧里运行字节码
所有栈帧都分配在堆内存上,堆内存不释放就会一直在内存中
这是python可以动态修改代码的基础,静态语言函数执行完立即释放内存
python函数的栈帧可以独立于调用者存在(反正都在内存中,只要有指针指着就行)
import inspect
frame = None
def foo():
bar()
def bar():
global frame
frame = inspect.currentframe()
'''获取栈帧'''
foo()
print(frame.f_code.co_name)
super_func = frame.f_back
print(super_func.f_code.co_name)
bar
foo
生成器函数也可以有return,但是每yield一次就有一个标记(字节码多一行),使得可以查看这个函数的上一个yield标记的值
生成器对象是对生成器的一层封装
4.生成器例子
当我们对类用for item in class:
来遍历,类里没有实现iter时,会生成默认迭代器再去找getitem。
for循环这种遍历方式的实现是
def __iter__(self):
i = 0
try:
while True:
v = self[i]'''getitem的方式'''
yield v
i += 1
except IndexError:
return
其中用了生成器和getitem实现
for循环实际是iter的体现,用来获取yield标记的值
5.生成器阅读大文件
一行带分隔符的数据,切成一条一条数据
def myreadlines(f, tic):
buf = ""
'''一块一块文件读'''
while True:
'''一段一段数据读'''
while tic in buf:
pos = buf.index(tic)
yield buf[:pos]
buf = buf[pos+len(tic):]
chunk = f.read(4096)# 往后在读4096byte
if not chunk:
'''读到末尾'''
yield buf
break
buf += chunk
with open("") as f:
for a_data in myreadlines(f, "{|}"):
print(a_data)