Python基础与拾遗8:Python中的迭代器与解析


本篇博文,主要总结Python中的数据类型与注意事项。在Python中,迭代器与解析是 Python高级程序员常用的技巧与手段。迭代器与解析充分体现了Python语言与C语言,Java语言,MATLAB等常用语言的区别,下面开始干货。

迭代器与解析的由来

在常用的所有编程语言中,for和while等关键字,都可以帮助我们完成循环重复的任务。但是在Python中,提供了迭代协议与解析工具,能使得迭代更高效,语法更简洁。尽管不是必须的,但是这些工具很有用并且强大,是高级Python程序员常用的技巧。

文件迭代器

为了在总结文件迭代器时更形象,笔者还是在计算机D盘下新建一个文本文档命名为test.txt,内容初始化如下所示:

aaaaaa
bbbbbb
cccccc
dddddd
eeeeee
ffffff
gggggg
  1. 1.可以使用==__next__函数进行迭代==,如果读到文件末尾,直接引发StopIteration异常。
f = open('D:/test.txt')
f.__next__() # 'aaaaaa\n'
f.__next__() # 'bbbbbb\n'
f.__next__() # 'cccccc\n'
f.__next__() # 'dddddd\n'
f.__next__() # 'eeeeee\n'
f.__next__() # 'ffffff\n'
f.__next__() # 'gggggg'
f.__next__() # StopIteration
  1. 使用for自动地调用next。注意,此时就不要使用readlines()方法了,因为readlines()是将整个文件加载进内存,从内存使用的角度来看,效果较差
    当然也可以用while语句,但是相比于for迭代器,运行更慢。因为for在Python中是以C语言的速度运行的,而while是使用Python虚拟机运行Python字节码的。
for line in open('D:/test.txt'):
	line = line.rstrip()
    print(line)
for line in open('D:/test.txt').readlines():
	line = line.rstrip()
    print(line)
'''输出如下
aaaaaa
bbbbbb
cccccc
dddddd
eeeeee
ffffff
gggggg
'''

Python 3.0及之后版本中的手动迭代:iter和next

  1. Python 3.0及之后的版本中包含内置函数next,自动调用一个对象的__next__方法。在for循环开始时,会通过它传给iter内置函数,以便从可迭代对象中返回一个迭代器,返回对象含有需要的next方法。比如文件对象的例子,文件对象就是自己的迭代器,下面的例子借用第一小节中创建的文件。
f = open('D:/test.txt')
next(f) # 'aaaaaa\n'
next(f) # 'bbbbbb\n'
next(f) # 'cccccc\n'
next(f) # 'dddddd\n'
next(f) # 'eeeeee\n'
next(f) # 'ffffff\n'
next(f) # 'gggggg'
next(f) # StopIteration
  1. 与文件对象不同,比如列表及其他很多的内置对象,就需要手动地先显式调用iter函数来启动迭代。注意,在Python 2.6及之后的版本中,还可以使用I.next(),Python 3.0及之后的版本中取消
L = [1, 2, 3]
I = iter(L)
type(I) # <class 'list_iterator'>
I.__next__() # 1
next(I) # 2

列表解析

  1. 例子:在Python解释器内部执行一个遍历列表L的迭代,按照顺序将x赋给每个元素,执行表达式的结果,最终返回一个新列表
L = [1, 2, 3]
L = [x +10 for x in L] # [11, 12, 13]
  1. 效率:列表解析比手动的for循环语句运行得更快,因为迭代在解释器内部是以C语言的速度执行的,而不是以手动Python代码执行的,特别是对于较大的数据集合
  2. 文件中的列表解析,下面的例子借用第一小节中创建的文件。
f = open('D:/test.txt')
lines = f.readlines() # ['aaaaaa\n', 'bbbbbb\n', 'cccccc\n', 'dddddd\n', 'eeeeee\n', 'ffffff\n', 'gggggg']
lines = [line.rstrip() for line in lines] # ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg']

上述列表解析可以结合文件迭代进行内存优化,不用提前打开文件。对于较大的文件,列表解析的优势更加显著。

lines = [line.rstrip() for line in open('D:/test.txt')] # ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg']
  1. 列表解析允许任意数目的for子句,每个子句有一个可选的相关的if子句。
[x + y for x in 'abc' for y in 'lmn'] # ['al', 'am', 'an', 'bl', 'bm', 'bn', 'cl', 'cm', 'cn']
  1. if子句。
[x **2 for x in range(10) if x % 2 ==0] # [0, 4, 16, 36, 64]
  1. 列表解析的应用例子:矩阵运算
M = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
N = [[2, 2, 2], [3, 3, 3], [4, 4, 4]]
[M[row][col] * N[row][col] for row in range(3) for col in range(3)] # [2, 4, 6, 12, 15, 18, 28, 32, 36]
[[M[row][col] * N[row][col] for row in range(3)] for col in range(3)] # [[2, 12, 28], [4, 15, 32], [6, 18, 36]]
  1. 列表解析和map内置函数,比for循环会快很多。好的Python程序员会尽量避免低效的手动循环。

生成器函数

  1. 生成器函数定义:编写常规的def语句,但是使用yield语句一次返回一个结果,在每个结果之间挂起和继续他们的状态
  2. 生成器函数返回按需产生结果的一个对象,而不是构造一个结果列表。
  3. 状态挂起:生成器函数在自动生成值的时刻挂起并继续函数的执行。生成器yield一个值,而不是返回一个值。yield语句挂起函数并向调用者发送回一个值,但保留足够的状态使得函数能从他离开的地方继续。
def gen(N):
    for i in range(N):
        yield i ** 2

def main():
    for i in gen(5):
        print(i)

if __name__ == '__main__':
    main()
'''
输出
0
1
4
9
16
'''
  1. 调用生成器函数时,返回一个迭代器对象,该对象支持一个__next__方法来继续执行接口,继续运行到下一个yield结果返回或者引发一个StopIteration异常。
def gen(N):
    for i in range(N):
        yield i ** 2

def main():
    x = gen(5)
    print(type(x)) # <class 'generator'>
    print(x.__next__()) # 0
    print(x.__next__()) # 1
    print(x.__next__()) # 4
    print(x.__next__()) # 9
    print(x.__next__()) # 16
    print(x.__next__()) # StopIteration

if __name__ == '__main__':
    main()
  1. 生成器函数也可能有一条return语句,总是在def语句块的末尾(一般不常用),直接终止生成。
def gen(N):
    for i in range(N):
        yield i ** 2

def main():
    for i in gen(5):
        print(i)

if __name__ == '__main__':
    main()
'''
输出
0
'''
  1. send方法。yield表达式会返回为了发送而传入的值,注意是用在生成器表达式中,即X = yield i,只有yield i 不管用。且用send方法前必须要用next()先初始化生成器
def gen(N):
    for i in range(N):
        x = yield i ** 2
        print(x)


def main():
    G = gen(5)
    next(G)
    r = G.send(77)
    print(r)
    r = G.send(88)
    print(r)
    r = next(G)
    print(r)

if __name__ == '__main__':
    main()
'''
输出
77
1
88
4
None
9
'''

def gen(N):
    for i in range(N):
        x = yield i ** 2
        print(x)

# 下面是没有初始化生成器的错误情况
def main():
    G = gen(5)
    # next(G)
    r = G.send(77)
    print(r)
    r = G.send(88)
    print(r)
    r = next(G)
    print(r)

if __name__ == '__main__':
    main()
'''
输出
TypeError: can't send non-None value to a just-started generator
'''
  1. 生成器是迭代对象。生成器函数与生成器表达式都是单迭代对象,只支持一个迭代器,两个会交替迭代
def gen(N):
    for i in range(N):
        yield i ** 2

def main():
    X = gen(5)
    I1, I2 = iter(X), iter(X)
    print(I1.__next__()) # 0
    print(I2.__next__()) # 1
    print(I1.__next__()) # 4
    print(I2.__next__()) # 9
    print(I1.__next__()) # 16
    print(I2.__next__()) # StopIteration

if __name__ == '__main__':
    main()

多个迭代器

  1. range对象支持len和索引,不是自己的迭代器(需要用iter手动返回迭代器)。支持多个迭代器,每个迭代器会记住各自的位置。
R = range(3)
I1 = iter(R)
I2 = iter(R)
next(I1) # 0
next(I2) # 0
  1. 列表,字典,元组,集合等内置类型支持多个迭代器
L = [1, 2, 3]
I1 = iter(L)
I2 = iter(L)
next(I1) # 1
next(I2) # 1
  1. zip,map和filter不支持相同结果中的多个活跃迭代器
Z = zip((1, 2, 3), (10, 11, 12))
I1 = iter(Z)
I2 = iter(Z)
next(I1) # (1,10)
next(I2) # (2,11)

其它迭代环境

  1. sorted排序可迭代对象中的各项,在Python 3.0及之后的版本中直接返回列表
L = [3, 2, 1]
I = iter(L)
sorted(I) # [1, 2, 3]
sorted(L) # [1, 2, 3]
  1. zip组合可迭代的对象中的各项,在Python 3.0及之后的版本中返回可迭代对象
L1 = [1, 2, 3]
L2 = [4, 5, 6]
I1 = iter(L1)
I2 = iter(L2)
zip(I1, I2) # <zip object at 0x0000025295FB2F80>
list(zip(I1, I2)) # [(1, 4), (2, 5), (3, 6)]
zip(L1, L2) # <zip object at 0x0000025295FBE4C0>
list(zip(L1, L2)) # [(1, 4), (2, 5), (3, 6)]
  1. enumerate根据相对位置配对可迭代对象中的各项,在Python 3.0及之后的版本中返回可迭代对象
L = [1, 2, 3]
I = iter(L)
enumerate(I) # <enumerate object at 0x0000025295FC3C00>
list(enumerate(I)) # [(0, 1), (1, 2), (2, 3)]
enumerate(L) # <enumerate object at 0x0000025295FAC680>
list(enumerate(L)) # [(0, 1), (1, 2), (2, 3)]
  1. filter选择一个函数为真的项,在Python 3.0及之后的版本中返回可迭代对象
L = [-3, -2, -1, 0, 1, 2, 3]
I = iter(L)
filter(bool, I) # <filter object at 0x0000025295F57910>
list(filter(bool, I)) # [-3, -2, -1, 1, 2, 3]
filter(bool, L) # <filter object at 0x0000025295FB8220>
list(filter(bool, L)) # [-3, -2, -1, 1, 2, 3]
  1. reduce针对可迭代对象中成对的项运行一个函数,在Python 3.0及之后的版本中直接返回结果。需要提供两个参数的函数,一个可迭代对象,和一个可选的初始化值。函数逻辑是依次从可迭代对象中取一个元素,和上一次调用函数的结果做参数再次调用函数。
reduce(lambda x, y: x + y, (1, 2, 3, 4)) # 10
reduce(lambda x, y: x + y, (1, 2, 3, 4), 100) # 110
  1. sum计算可迭代对象中的总数
L = [1, 2, 3]
I = iter(L)
sum(I) # 6
sum(L) # 6
  1. any和all针对可迭代对象中任何所有项为真时返回True。注意,是直接针对可迭代对象进行判断,不是判断迭代器对象,见下方例子。
L = [0, 1, 2, 3]
I = iter(L)
any(I) # True
any(L) # True
all(I) # True
all(L) # False
  1. max和min分别返回一个可迭代对象中的最大和最小项。注意,是直接针对可迭代对象进行判断,不是判断迭代器对象,见下方例子。
L = [0, 1, 2, 3]
I = iter(L)
max(I) # ValueError: max() arg is an empty sequence
max(L) # 3
min(I) # ValueError: min() arg is an empty sequence
min(L) # 0

以上,欢迎各位读者朋友提出意见或建议。

欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!

written by jiong
我从来不想独身,
却有预感晚婚。
我在等,
世上唯一契合灵魂。

猜你喜欢

转载自blog.csdn.net/jiongnima/article/details/113729581
今日推荐