Python 迭代器之列表解析

2022-12-19,,,

尽管while和for循环能够执行大多数重复性任务, 但是由于序列的迭代需求如此常见和广泛, 以至于Python提供了额外的工具以使其更简单和高效. 迭代器在Python中是以C语言的速度运行的, 而且迭代器的版本根据每次发行而改进, 所以它的运行速度更快.

while一般比for的效率更低些, for循环通过不同的写法可以完全可以替代while循环, 所以当在while和for中选择时,用for循环吧

迭代器

迭代器初探

for循环可以用于Python中任何序列类型, 包括列表以及元组以及字符串. 实际上for循环更加通用:可用于任何可迭代对象.实际上, python中所有的会从左到右扫描对象的迭代工具都是如此通用, 这些迭代工具包括 for循环, 列表解析, in成员关系测试, map内置函数等

>>> for i in [1, 2, 3]:print(i, end=' ')
1 2 3 >>> for i in (1, 2, 3):print(i, end=' ')
1 2 3 >>> for i in "apple":print(i, end=' ')
a p p l e

可迭代对象

序列概念的通用化,如果对象是实际保存的序列或者迭代环境中一次产生一个结果的对象, 就可以看做是可迭代的

文件迭代器

看看迭代器和内置类型如何工作的,例如文件, 已经打开的文件对象有个方法readline(), 可以一次从文本中读取下一行(包括行末标识符)到一个字符串, 每次调用readline(),就会前进到下一行, 到达文件末尾返回空字符串, 我们通过它来检测, 从而跳出循环.

>>> f = open("/Users/mac/Documents/path.py")
>>> f.readline()
'import sys\n'
>>> f.readline()
'print(sys.path)\n'
>>> f.readline()
'print("hello heathty")\n'
>>> f.readline()
''
>>> f.readline()
''

文件也有一个方法为 __next__(), 每次调用就会返回文件的下一行, 应该注意的区别是到达文件末尾 __next__()会引发内置的StopIteration异常, 而不是空字符串

>>> f = open("/Users/mac/Documents/path.py")
>>> f.__next__()
'import sys\n'
>>> f.__next__()
'print(sys.path)\n'
>>> f.__next__()
'print("hello heathty")\n'
>>> f.__next__()
StopIteration
>>>

这个接口就是Python的迭代协议:

1. 有__next__() 方法的对象会前进到下一个结构, 而在末尾会引发StopIteration异常.

2. 在Python中任何这类对象都认为是可迭代的.任何这类对象也能以for循环或者其他迭代工具遍历

3. 所有的迭代工具内部工作都是在每次迭代中调用__next__(), 并且捕捉StopIteration异常来确定何时离开

文件对象有个方法readlines(), 它会在调用时一次把所有的文件加载到内存, 如果文件太大, 以至于计算机内存不够, 甚至不能工作; 而迭代器一次读取一行到内存,可以避免这个问题

>>> for line in open("/Users/mac/Documents/path.py").readlines():
print(line.upper(), end=' ')
IMPORT SYS
PRINT(SYS.PATH)
PRINT("HELLO HEATHTY") # 迭代器
>>> for line in open("/Users/mac/Documents/path.py"):
print(line.upper(), end=' ') IMPORT SYS
PRINT(SYS.PATH)
PRINT("HELLO HEATHTY")

手动迭代: iter 和 next

为了支持手动迭代代码, Python3.0提供了内置函数next(), 它会自动调用一个对象的__next__()方法, 调用next(X) 等同于X.__next__(),

>>> f = open("/Users/mac/Documents/path.py")
>>> next(f)
'import sys\n'
>>> next(f)
'print(sys.path)\n'
>>> next(f)
'print("hello heathty")\n'
>>> next(f)
StopIteration
>>>

技术角度来说迭代协议还有一点值得注意. 当for循环开始的时, 会通过for可迭代对象传递给iter内置函数, 以便可迭代对象获取一个迭代器, 返回对象含有__next__()方法.

我们下面看看for循环内部如何处理列表这类内置类型吧

>>> L = [1, 2, 3]
>>> L is iter(L)
False
>>> L.__next__()
AttributeError: 'list' object has no attribute '__next__'
>>> I = iter(L)
>>> I.__next__()
1
>>> I.__next__()
2
>>> I.__next__()
3
>>> I.__next__()
StopIteration
>>>

这一步 I = iter(L) 对于文件来说不是必须的, 因为文件自己就是自己的迭代器, 也就是说文件自己就有__next__()方法, 因此不需要返回一个不同的对象:

>>> f = open("/Users/mac/Documents/path.py")
>>> iter(f) is f
True
>>> f.__next__()
'import sys\n'
>>>

列表以及很多其他内置对象,不是自身的迭代器, 因为它们支持多次打开迭代器, 对这样的对象, 我们必须要iter()内置函数来启动迭代

注意字典中 , 遍历字典的经典方法是明确的获取字典的键的列表; 在Python3.0中, 字典有一个迭代器在迭代环境中,会自动一次返回一个键.


>>> D = dict(a=1, b=2, c=3)
>>> D
{'c': 3, 'b': 2, 'a': 1}
>>> for k in D.keys():print(k)
c
b
a
>>> for k in D:print(k)
c
b
a
>> I = iter(D)
>>> next(I)
'c'
>>> next(I)
'b'
>>> next(I)
'a'
>>> next(I)
StopIteration
>>>

列表解析

列表解析是最常用的迭代协议之一, 经常与for循环一起使用,下面来看看例子

一般修改列表时, 通过遍历列表的元素, 使用range来修改它; 现在有个更加简洁的方法来达到, 我们可以通过产生所需列表的一个单个表达式来替换该循环.

>>> L = [1, 1, 2, 3, 5]
>>> for i in range(len(L)):
L[i] += 2
>>> L
[3, 3, 4, 5, 7] >>> L = [i + 10 for i in L]
>>> L
[13, 13, 14, 15, 17]
>>>

上述两种方式结果相同, 但是列表解析更加简洁, 并且可能运行更快, 列表解析并不完全和for循环语句版本相同, 因为它会产生一个新的列表对象; 此外列表解析比手动的for循环语句运行更快(大约一倍), 因为他们的迭代在解释器内部是以C语言的速度执行.

列表解析基础知识

 L = [i + 10 for i in L]

1. 组成:

列表解析写在一个方括号 [ ]中, 因为它们是最终构建一个新的列表的一种方式;
它以我们组成的一个任意表达式开始, 该表达式使用后面for循环申明的循环变量;
后面的for循环申明循环变量, 以及一个可迭代对象

2. 运行

运行该表达式, Python在解释器内部执行一个遍历L的迭代, 按照顺序把 i 赋值到表达式中, 并且收集表达式的运算结果, 得到一个新列表.

扩展的列表解析语法

作为一个特别有用的扩展, 表达式中嵌套的for循环可以有一个相关的if字句, 用来过滤那些测试结果不为真的项.

>>> L = [4, 5, 7, 9, 12]
>>> [i for i in L if(i%4 ==0)]
[4, 12]
>>>

如果我们需要的话, 列表解析可以更加复杂:

他们可以包含任意嵌套的循环, 也可能被编写为一系列子句.实际上, 他们完整的语法允许任意数目的子句, 每个子句有一个可选的相关if子句

>>> [x+y for x in "ab" for y in "mn"]
['am', 'an', 'bm', 'bn']

其他迭代环境

map:

    内置函数, 它把一个函数调用应用于传入的可迭代对象中的每一项,
    它有局限性, 因为它需要一个函数,而不是一个任意表达式.
    Python3.0中 map返回一个可迭代对象自身, 因此我们需要将它包含到一个list调用已迫使一次性给出所有值

sorted排序可迭代对象的各项;

zip组合可迭代对象的各项;

enumerate根据相对位置匹配可迭代对象的项;

filter选择一个函数为真的项;

reduce针对可迭代对象的成对的项运行一个函数;

range() 内置函数, 返回可迭代对象,

所有这些接受一个可迭代对象, Python3.0中zip,enumerate, filter也像map一样返回一个可迭代对象. 他们都是自己的迭代器----在遍历结果一次之后, 游标就到底了, 没法在此使用__next__()方法了

>>> f = open("/Users/mac/Documents/path.py")
>>> sorted(f)
['import sys\n', 'print("hello heathty")\n', 'print(sys.path)\n'] >>> list(zip(open("/Users/mac/Documents/path.py"), open("/Users/mac/Documents/path.py")))
[('import sys\n', 'import sys\n'), ('print(sys.path)\n', 'print(sys.path)\n'), ('print("hello heathty")\n', 'print("hello heathty")\n')] >>> list(enumerate(open("/Users/mac/Documents/path.py")))
[(0, 'import sys\n'), (1, 'print(sys.path)\n'), (2, 'print("hello heathty")\n')]
>>> >>> l = [0, 1, 3]
>>> list(filter(bool, l))
[1, 3]
>>>
>>> import functools ,operator
>>> functools.reduce(operator.add, l)
4

多个迭代器 vs 单个迭代器

range 它不是自己的迭代器(手动迭代, 我们使用iter 产生一个迭代器), 并且它支持器结果上的多个迭代器, 这些迭代器会记住各自的位置:

>>> R = range(3)
>>> R
range(0, 3)
>>> next(R)
TypeError: 'range' object is not an iterator
>>> I1 = iter(R)
>>> next(I1)
0
>>> next(I1)
1
>>> I2 = iter(R)
>>> next(I2)
0
>>>

zip, map, filter不支持相同结果上的多个活跃迭代器

>>> z = zip([1, 2, 3],['a', 'b', 'c'])
>>> list(z)
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> I1 = iter(z)
>>> I2 = iter(z)
>>> next(I1)
StopIteration # 由于上面list(z), 让迭代器的游标知道底部了 >>> z = zip([1, 2, 3],['a', 'b', 'c'])
>>> I1 = iter(z)
>>> I2 = iter(z)
>>> next(I1)
(1, 'a')
>>> next(I1)
(2, 'b')
>>> next(I2)
(3, 'c')
>>> next(I2)
StopIteration

字典视图迭代器

Python3.0中, 字典的keys(), values(), items()方法返回字典视图对象, 他们每次产生一个结果, 而不是在内存中一次产生全部的可迭代对象.

视图保持了和字典中那些项相同的物理顺序, 并且反映底层的字典做出的修改.

下面以keys()方法为例说明, values()与items()与之相同


>>> D = dict(a=1, b=2, c=3)
>>> D
{'b': 2, 'a': 1, 'c': 3}
>>> K = D.keys()
>>> K
dict_keys(['b', 'a', 'c'])
>>> next(K)
TypeError: 'dict_keys' object is not an iterator
>>> I = iter(K)
>>> next(I)
'b'
>>>

Python 迭代器之列表解析的相关教程结束。

《Python 迭代器之列表解析.doc》

下载本文的Word格式文档,以方便收藏与打印。