概述
一、概述
在学习python的数据结构的时候,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一起,容易让人晕头转向
二、容器(container)
容器是一种将多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in和not in
来判断元素是否在容器中存在。这类数据结构大都把所有的元素存储在内存中,python常见的容器有:
- list ; deque;......
- set ; frozensets ; ......
- dict ; defaultdict ; OrderedDict ; Counter ; ......
- tuple ; namedtuple; ......
- str
所谓容器,就是字面上的意思--容器,在python这种弱类型的语言中,可以往容器中放任何东西。基本上所有有元素的数据类型(字符串除外)都能包含其他类型的对象;从技术角度来说,当一个对象可以用来询问某个元素是否存在于这个对象中的时候,这个对象就是一个容器。尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是 可迭代对象 赋予了容器这种能力,当然并不是所有的容器都是可迭代的。
三、可迭代(Iterable)对象
可迭代对象并不是指某种具体的数据类型,它是指存储了元素的一个容器对象,且容器中的元素可以通过__iter__( )方法或__getitem__( )方法访问。如果给定一个list或者tuple,我们可以通过for等方式来遍历这个list或者tuple,这种遍历被称为迭代(Iteration)。
3.1、迭代器与可迭代对象
上面提到,很多容器都是可迭代对象,此外还有很多的对象同样也是可迭代对象,例如打开状态下的files,sockets等。只要能够返回一个迭代器的对象都可以称之为可迭代对象,如下示例:
这里的x就是一个可迭代的对象,可迭代对象并不是指某种具体的数据类型,它是指存储了元素的一个容器对象。y、z是两个迭代器对象。可迭代对象实现了__iter__()和__next__()(python2中是next()),分别对应内置函数iter()和next()。
所有的Iterable可迭代对象均可以通过内置函数iter()来转变为迭代器Iterator。可以这么理解,__iter__( )方法是让对象可以用for ... in循环遍历时找到数据对象的位置,__next__( )方法是让对象可以通过next(实例名)访问下一个元素。除了通过内置函数next调用可以判断是否为迭代器外,还可以通过collection中的Iterator类型判断。如: isinstance('', Iterator)可以判断字符串类型是否迭代器。注意: list、dict、str虽然是Iterable,却不是Iterator。如果实现了__iter__()则是一个可迭代对象,实现了__iter__(),和__next__() (python2中实现 next() )方法的对象则即是可迭代对象也是迭代器。
from _collections_abc import Iterator, Iterable
if __name__ == '__main__':
print(isinstance([1,2,3], Iterator))
print(isinstance([1,2,3], Iterable))
'''
输出结果:
False
True
'''
3.2、迭代器
在迭代器的内部持有一个状态,该状态用于记录代签迭代所在的位置,以方便下次迭代的时候获取正确的元素。其中迭代器有具体的迭代器类型,例如:list_iterator,set_iterator。能在你不断调用内置函数 next() 的时候不断返回容器中的下一个值,直到没有数据的时候抛出StopIteration错误。
3.2.1、创建迭代器(斐波那切数列示例)
class Fab(object):
def __init__(self,m):
self.max = m
self.n,self.a,self.b = 0 , 0 , 1
def __iter__(self):
return self
def __next__(self):
if self.n < self.max:
r = self.a
self.a,self.b = self.b,self.a+self.b
self.n += 1
return r
raise StopIteration()
if __name__ == '__main__':
for i in it:
print(i,end=' ')
'''
输出结果:
0 1 1 2 3 5 8 13 21
亲测:
当注释掉__iter__()方法之后,Fab(9)既不可迭代也不是一个迭代器,即
print(Fab(9),Iterable)
print(Fab(9),Iterator)
输出全是False
注释掉__next__()则是可迭代的对象,即
print(Fab(9),Iterable)输出True
print(Fab(9),Iterator)输出False
'''
Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数,每次调用 next() 方法的时候做两件事:
- 1、为下一次调用 next() 方法修改状态
- 2、为当前这次调用生成返回结果
- 迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
3.2.2、for i in (iterator)的内部实现
for首先会调用类中__iter__()方法获取迭代器对象,类似于手动调用内置函数it = iter(...),然后利用这个迭代器对象调用__next__()方法,不断获取下一个值,直到结束。类似示例:
for i in it:
print(i,end=' ')
'''
就相当于:
'''
it = iter(Fab(9))
while True:
try:
print(next(it),end=' ')
except StopIteration as e:
pass
四、生成器
生成器其实是一种特殊的迭代器,使用关键字yield,使其不再像上面的类一样写 __iter__() 和 __next__() 方法了,生成器也是以一种懒加载的模式生成值。
4.1、yield关键字
4.1.1、看起来像return
菜鸟刚起飞时,可以将yield看做“return”,就是会返回yield后面的值,这里的返回并不是真的将yield后的值当做函数返回值(函数的返回值是一个生成器)直接返回,而是返回到了一个可迭代的容器中,将这个容器返回,同时这个容器也是一个迭代器,使用next()取到这个值,这里的返回并不会使函数体停止,而是使用一个状态记录迭代位置(就像3.2中所描述的那样),等待下一个next()。示例:
示例一:
def foo():
yield 4
print(next(foo()),type(foo()))
'''
输出结果:
4 <class 'generator'>
'''
示例二、
def foo():
yield 4
yield 5
print('-'*15,end=' ')
yield 6
if __name__ == '__main__':
g = foo()
print(next(g),end=' ')
print(next(g),end=' ')
'''
输出结果:
4 5
'''
def foo():
yield 4
yield 5
print('-'*15,end=' ')
yield 6
if __name__ == '__main__':
g = foo()
print(next(g),end=' ')
print(next(g),end=' ')
print(next(g),end=' ')
'''
输出结果:
4 5 --------------- 6
'''
4.1.2、有yield的函数到底是怎么运行的
'''
以下各个实例中,不同的地方都在
if __name__ == '__main__':中
'''
def foo():
print('start() foo')
yield 4
print('after yield 4')
yield 5
print('after yield 5')
yield 6
print('after yield 6')
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
'''
输出结果:
start main
after g = foo()
也就是说在g=foo()的时候函数体并没有执行
'''
def foo():
print('start() foo')
yield 4
print('after yield 4')
yield 5
print('after yield 5')
yield 6
print('after yield 6')
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
print(next(g),end=' ')
'''
输出结果:
start main
after g = foo()
start() foo
4
'''
def foo():
print('start() foo')
yield 4
print('after yield 4')
yield 5
print('after yield 5')
yield 6
print('after yield 6')
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
print(next(g),end=' ')
print(next(g),end=' ')
print(next(g),end=' ')#在这里输出6之后,并不会执行print('after yield 6')
print(next(g),end=' ')
'''
输出结果:
start main
after g = foo()
start() foo
4 after yield 4
5 after yield 5
6 after yield 6
Traceback (most recent call last):
File "D:ProjectsFirstPythonzz疑难杂症new1_yield关键字.py", line 45, in <module>
print(next(g),end=' ')
StopIteration
当yield后的数被全部取出后,再尝试取数的时候,就会抛出错误!
'''
总结:
①程序开始执行的时候,由于函数中由yield关键字,函数体并不会立即执行,而是返回一个叫做生成器的迭代器(迭代器不一定是生成器,但生成器肯定是迭代器);
②当程序中显式或者隐式地调用next()(直接使用next(),或者调用含有next()函数的函数例如下面提到的send()),生成器函数体才会真正开始执行-----第一次调用next(),将yield关键字后面的对象返回,并停留在第一个yield位置,依次类推;
③当所有yield后的对象全部被取出之后,再调用next(),则会抛出StopIteration错误。
4.2、生成器函数体中有return
当生成器函数体中执行到return时,直接抛出StopIteration错误,结束迭代。示例:
def foo():
print('start() foo')
yield 4
return
print('after yield 4')
yield 5
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
print(next(g),end=' ')
try:
print(next(g),end=' ')
except StopIteration as e:
print('n异常被捕获,结束迭代')
'''
输出结果:
start main
after g = foo()
start() foo
4
异常被捕获,结束迭代
'''
4.3、send()函数
send(实例)会将实参传递到yield位置,在send()中有一个next(),并将next()取出的对象返回。示例如下:
def foo():
print('this is in foo()')
res = yield 4
yield res
yield 5
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
print(next(g))
print(g.send(56))#在send()中有一个next(),并将next()取出的对象返回
print(next(g))
'''
输出结果:
start main
after g = foo()
this is in foo()
4
56
5
'''
'''
如果第一次先调用send()而不调用next(),则会报错
'''
if __name__ == '__main__':
print('start main')
g = foo()
print('after g = foo()')
print(g.send(56))#在send()中有一个next
print(next(g))
print(next(g))
'''
输出结果:
start mainTraceback (most recent call last):
after g = foo()
File "D:ProjectsFirstPythonzz疑难杂症new1_yield关键字.py", line 18, in <module>
print(g.send(56))#在send()中有一个next
TypeError: can't send non-None value to a just-started generator
'''
需要注意的是:当在send()前使用next()的时候,next()只会讲yield后的对象返回,并停留在对应yield位置,赋值操作并不会执行。换个角度想,对象还没send(),赋值也不可能执行。
4.3、为什么使用生成器?
4.3.1、列表生成式
使用列表生成式可以快速创建一个列表。[]会返回一个列表,而()会返回一个生成器。
a = [x for x in range(10)]
b = [x*2 for x in range(10) if x%2==0]
c = (x for x in range(10))
d = (x*2 for x in range(10) if x%2==0)
print(type(a),"a=",a)
print(type(b),"b=",b)
print('typec>>',type(c))
print('typed>>',type(d))
for i in c:
print(i,end = ' ')
print()
for i in d:
print(i,end = ' ')
'''
输出结果:
<class 'list'> a= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<class 'list'> b= [0, 4, 8, 12, 16]
typec>> <class 'generator'>
typed>> <class 'generator'>
0 1 2 3 4 5 6 7 8 9
0 4 8 12 16
'''
4.3.2、原因
有4.3.1中的示例可见,range()并不是一个生成器,所以,它产生的所有元素将全部存储在内存中。当元素数据巨大时,占用的内存容量自然很大。使用yield来实现,则会节省一部分空间。例如:
def foo(num,n):
while num<n:
num=num+1
yield num
a = [x for x in foo(0,5)]
print(a)
'''
输出结果:
[1, 2, 3, 4, 5]
'''
五、几个实例
5.1、读取文件
5.1.1、每次读取固定长度。
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
5.1.2、输出最长行的长度
对于文件句柄,也是一个可迭代对象。在for循环内部使用迭代器来读取。
print(max(len(x.strip()) for x in open('/hello/abc','r')))
5.2、一个caodan的示例
def add(s, x):
print("in add>>>%d+%d=%d"%(s,x,s+x))
return s + x
def gen():
for i in range(4):
print('in gen()>>>',i)
yield i
base = gen()
for n in [1,10]:
print("n in for >>>",n)
base = (add(i, n) for i in base)
print(list(base))
'''
输出结果:
n in for >>> 1
n in for >>> 10
in gen()>>> 0
in add>>>0+10=10
in add>>>10+10=20
in gen()>>> 1
in add>>>1+10=11
in add>>>11+10=21
in gen()>>> 2
in add>>>2+10=12
in add>>>12+10=22
in gen()>>> 3
in add>>>3+10=13
in add>>>13+10=23
[20, 21, 22, 23]
base = (add(i, n) for i in base)这个东西返回一个生成器
'''
5.3、实现多次迭代
我们知道iterator只能迭代一次,但是iterable对象则没有这个限制,因此我们可以把iterator从数据中分离出来,分别定义一个iterable与iterator如下:
class Data(object): # 只是iterable:可迭代对象而不iterator:迭代器
def __init__(self, *args):
self.data = list(args)
def __iter__(self): # 并没有返回自身
return DataIterator(self)
class DataIterator(object): # iterator: 迭代器
def __init__(self, data):
self.data = data.data
self.ind = 0
def __iter__(self):
return self
def __next__(self):
if self.ind == len(self.data):
raise StopIteration
else:
data = self.data[self.ind]
self.ind += 1
return data
if __name__ == '__main__':
d = Data(1, 2, 3)
for x in d:
print(x,end=' ')
print('n')
for x in d:
print(x,end=' ')
'''
输出结果:
1 2 3
1 2 3
'''
六、参考文章
https://www.cnblogs.com/yuanchenqi/articles/5769491.html
https://blog.csdn.net/mieleizhi0522/article/details/82142856/
https://www.jb51.net/article/80740.htm
最后
以上就是知性黑裤为你收集整理的Python菜鸟起飞day7_迭代器与生成器(5.2未想通)一、概述二、容器(container)三、可迭代(Iterable)对象四、生成器五、几个实例六、参考文章的全部内容,希望文章能够帮你解决Python菜鸟起飞day7_迭代器与生成器(5.2未想通)一、概述二、容器(container)三、可迭代(Iterable)对象四、生成器五、几个实例六、参考文章所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复