assastor 2020-03-20
我们都知道,在Python中,我们可以for循环去遍历一个列表,元组或者range对象。
for i in [1,2,3]: print(i) for i in range(0,10): print(i)
那底层的原理是什么样的呢?这其中涉及到了几个概念,“可迭代”,“迭代器”,“生成器”等,大部分人可能听过这些名词,但是他们具体的含义以及之间的关系可能没搞清楚,以下就是它们之间的关系图,接下来我们就来分析这个关系图。
如果一个对象是可迭代对象,那么我们就可以用for循环去遍历它,比如列表、元组、字符串等都是可迭代对象。而我们用for循环去遍历它的原理就是,先获取了它的迭代器,然后使用迭代器的next方法去逐一遍历。
a = [1,2,3] # for相当于下面的代码 for i in a: print(i) # for循环分解(实际是通过Python底层C语言实现的,此处只是演示) ## 第一步: 获取迭代器 iterator_a = iter(a) ## 第二步: 通过next逐个遍历 while True: try: print(next(iterator_a)) except StopIteration: ## 第三步:遇到StopIteration异常,停止 break
注意可迭代对象与迭代器的区别,如果一个对象是可迭代对象,那么我们就肯定能调用iter()方法获取它的迭代器,而如果一个对象是迭代器,我们就能用next()方法去拿下一个元素。 我们可以用isinstance判断一个对象是不是可迭代对象,是不是迭代器。
>>> from collections.abc import Iterable >>> from collections.abc import Iterator >>> isinstance([1,2,3],Iterable) True >>> isinstance([1,2,3],Iterator) False >>> i = iter([1,2,3]) >>> isinstance(i, Iterable) True >>> isinstance(i, Iterator) True >>> type(i) <class ‘list_iterator‘>
列表本身不是迭代器,它是可迭代对象,所以你不能用next()操作列表
>>> a=[1,2] >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ‘list‘ object is not an iterator >>> iter_a = iter(a) >>> next(iter_a) 1 >>> next(iter_a) 2 >>> next(iter_a) # 当没有剩余元素时,就抛出StopIteration异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
可迭代对象必须实现__iter__()函数,返回迭代器,调用对象自身的__iter__()函数与将iter()作用于对象效果是一样的,同理对__next__()和next()也一样。
>>> a=[1,2,3] >>> iter_a = a.__iter__() >>> next(iter_a) 1 >>> iter_a.__next__() 2
有趣的是,迭代器也是一个可迭代的对象,所以它本身也需要实现__iter__()函数,但是,一个迭代器的迭代器,是它本身,所以可能也有些多余了。
>>> a=[1,2,3] >>> iter_a = iter(a) >>> iter_b = iter(iter_a) >>> iter_c = iter(iter_b) >>> iter_a is iter_b True >>> iter_a == iter_c True >>> for x in iter_a: ... print(x) ... 1 2 3
我们经常会用到一些内置的迭代器,例如filter和map,注意range是可迭代对象,但不是迭代器。
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=range(10) >>> isinstance(b, Iterable) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name ‘b‘ is not defined >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) False >>> type(a) <class ‘range‘> >>> print(a) range(0, 10)
filter函数用于对一个列表进行过滤,传入一个函数和列表,这个函数返回值是True或者False,将列表的元素逐个传入这个函数,结果为True的保留,可以使用lambda函数。
注:在Python2.x中返回值为list,在Python3.x中返回迭代器
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6]) >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) True >>> type(a) <class ‘filter‘> >>> print(a) <filter object at 0x000001A6100A2948> >>> for i in a: ... print(i) ... 2 4 6
如果我们要想通过下表访问,可以把它转换成list
>>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6]) >>> a = list(a) >>> a[0] 2
map函数接收一个函数与一个列表,将这个函数作用域列表的每个元素,生成一个新的序列,返回迭代器。
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=map(lambda x : x * x, [1,2,3]) >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) True >>> type(a) <class ‘map‘> >>> print(i) File "<stdin>", line 1 print(i) ^ IndentationError: unexpected indent >>> print(a) <map object at 0x000001A6100C5108> >>> for i in a: ... print(i) ... 1 4 9
我们将list做一个简单的封装,实现一个可迭代的mylist。
class mylist: def __init__(self, l): self.l = l def __iter__(self): return mylist_iterator(self.l) class mylist_iterator: def __init__(self, l): self.l = l self.current = 0 # 记录当前迭代到哪个元素了 def __iter__(self): # 迭代器的迭代器返回自己即可 return self def __next__(self): if self.current < len(self.l): self.current += 1 return self.l[self.current-1] else: raise StopIteration a = mylist([1,2]) for x in a: print(x) i = iter(a) print(next(i)) print(next(i)) print(next(i))
上述代码并没有实现迭代器带来的好处,因为我们事先传入了一个列表进去,假如这个列表很大,会占内存。假如我们要实现一个类似range()功能,我们可以使用更有效的方法。
class myrange: def __init__(self, max_num): self.max_num = max_num def __iter__(self): return myrange_iterator(self.max_num) class myrange_iterator: def __init__(self, max_num): self.max_num = max_num self.current = 0 # 记录当前迭代到哪个元素了 def __iter__(self): # 迭代器的迭代器返回自己即可 return self def __next__(self): if self.current < self.max_num: self.current += 1 return self.current-1 else: raise StopIteration a = myrange(2) for x in a: print(x) i = iter(a) print(next(i)) print(next(i)) print(next(i))
需要注意的是,我们的myrange不能随机访问,只能一次性顺序遍历,只能前进,不能后退,实际Python的range()可以随机访问。
这个答案是No。for循环大部分情况都作用于可迭代对象,但是有一个例如,如果对象是可以通过下标访问的,也能用于for循环。
一个对象如果不能用下标访问,那么就会报下面的错误,实际上它对应的是一个__getitem__()内置方法。
>>> a={1,2,3} >>> a[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ‘set‘ object is not subscriptable >>> a.__getitem__(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: ‘set‘ object has no attribute ‘__getitem__‘
如果我们实现了__getitem__(),也能通过for循环遍历。
from collections.abc import Iterator from collections.abc import Iterable class mylist1: def __init__(self, l): self.l = l def __getitem__(self, i): return self.l[i] print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) a = mylist1([1,2,3]) for x in a: print(x)
结果如下,可以看到a既不是可迭代对象,也不是迭代器。
False False 1 2 3
for循环会先看对象是不是实现了__iter__(),如果是,就用迭代器的方式,如果没有的话,就看有没有__getitem__(),都没有就报错,报的错如下:
>>> for x in 5: ... print(x) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ‘int‘ object is not iterable
那我们怎么知道它先去找__iter__()呢?我们在前面的代码里加上几行,
from collections.abc import Iterator from collections.abc import Iterable class mylist1: def __init__(self, l): self.l = l def __getitem__(self, i): return self.l[i] def __iter__(self): return self print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) a = mylist1([1,2,3]) for x in a: print(x)
结果如下:
True False Traceback (most recent call last): File "<ipython-input-13-8e65b9d361a6>", line 1, in <module> runfile(‘C:/Users/xlniu/test123/test.py‘, wdir=‘C:/Users/xlniu/test123‘) File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile execfile(filename, namespace) File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile exec(compile(f.read(), filename, ‘exec‘), namespace) File "C:/Users/xlniu/test123/test.py", line 49, in <module> for x in a: TypeError: iter() returned non-iterator of type ‘mylist1‘
接下来我们看关系图的左边,生成器,生成器是迭代器,迭代器是可迭代对象,所以生成器肯定是可迭代对象了。哪些对象是生成器呢?
生成器的来源主要有两个,一个是生成器表达式,例如(i for i in ‘hello, world‘), (i for i in range(0,10) if i % 2 == 0),另一个是生成器函数,生成器函数不使用return返回数据,而使用yield。
我们来看一下前面说的filter是不是生成器。
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> from collections.abc import Generator >>> a = [1,2,3,4,5,6] >>> b = filter(lambda x : x % 2 == 0, a) >>> print(isinstance(b, Generator)) False
它并不是一个生成器。
生成器表达式与列表推断是差不多的,但是它用"()"括起来,而列表推断用的中括号,一般的语法就是:
expr(val) for val in xxx if yyy
例如
>>> from collections.abc import Generator >>> a = (i for i in range(0, 10)) >>> next(a) 0 >>> next(a) 1 >>> print(isinstance(a, Generator)) True >>> print(type(a)) <class ‘generator‘> >>> print(a) <generator object <genexpr> at 0x000001A61011B7C8> #通过genexpr得到的生成器 >>> b = (i.upper() for i in ‘hello, world‘) >>> c = (x for x in range(0,10) if x % 5 == 0) >>> for x in b: ... print(x) ... H E L L O , W O R L D >>> d = [i for i in range(0,10)] >>> print(type(d)) >>> type(d) <class ‘list‘> >>> print(d) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果我们把生成器表达式用在其他的对象上,例如set,list等,它们会自动转换成相应类型。
>>> set(i for i in range(0,5)) # 相当于set( (i for i in range(0,5)) ) {0, 1, 2, 3, 4} >>> set( (i for i in range(0,5)) ) {0, 1, 2, 3, 4} >>> tuple(i for i in range(0,5)) (0, 1, 2, 3, 4) >>> list(i for i in range(0,5)) [0, 1, 2, 3, 4]
另外一种生成器通过生成器函数得到。
from collections.abc import Iterator from collections.abc import Iterable from collections.abc import Generator def myrange(stop): i = 0 while i < stop: yield i #返回i,下次调用时会从这个地方继续向下执行 i += 1 e = myrange(5) print(isinstance(e, Iterable)) print(isinstance(e, Iterator)) print(isinstance(e, Generator)) print(type(e)) print(e) print(next(e)) for x in e: print(x)
运行结果如下:
True True True <class ‘generator‘> <generator object myrange at 0x000001CEC7342C48> 0 1 2 3 4
在函数myrange中,有一个特殊的关键词,yield。这个与return类似,但是return后,下次调用会从头开始,但是使用了yield,我们的函数就会返回一个生成器,相当于每次执行,都记住了上次的位置,从上次的位置继续执行。生成器表达式可以认为是一种特殊的生成器函数,类似于lambda表达式和普通函数。但是和生成器一样,生成器表达式也是返回生成器generator对象,一次只返回一个值。