python——迭代和解析

一般来说,生成器函数和常规函数一样,并且实际上也是用常规的def语句编写的。然而,当创建时,他自动实现迭代协议,以便可以出现在迭代背景中。

0.迭代协议

有__next__方法的对象会前进到下一个结果,当到达结尾时则会引发StopIteration异常。在python中,任何这类对象都认为是可迭代的。任何这类对象也能以for循环或其他迭代工具遍历,因为所有迭代工具内部工作起来都在每次迭代中调用__next__,并且捕捉StopIteration异常来确定何时离开。

# 读取文本文件的最佳方式就是根本不要去读取;代替的办法是让for循环在每轮自动调用next从而前进到下一行
# 这是读取文本文件的最佳方式,因为写法最简单,运行最快,并且从内存使用情况来说也是最好的。
# 这个和for line in f.readlines是一个效果
l =list()
with open('../data/123.txt') as f:
    # a = f.readline()
    for line in f:
        print(line, end='')  # end=''是为了抑制一个\n 因为已经有一个\n了
        l.append(line)
    print(l)

 

1.手动迭代:iter和next

python3提供了一个内置函数next,它会自动调用一个对象的__next__方法。给定一个可迭代对象X,调用next(X)等同于X.__next__()

手动迭代的方法就是先创建一个迭代器(iter),然后通过迭代器的next方法进行迭代。具体做法如下:

D = {'a':'A','b':'B','c':'C'}
I = iter(D)
print(next(I)) # a
print(next(I)) # b

但是对于文件来说,是不需要初始化迭代器的(上面代码第二行),因为文件对象就是自己的迭代器。也就是说,文件有自己的__next__方法,因此不需要像这样返回一个不同的对象。列表以及很多其他的内置对象,不是自身的迭代器,因为它们支持多次打开迭代器。对这样的对象,我们必须调用iter来启动迭代,他们(dict, list)没有自己的__next__方法

  

D = {'a':'A','b':'B','c':'C'}
I = iter(D)
if iter(D) is D:
    print('true')
elif iter(D) is I:
    print('I is iter')  # 我本来以为会打印这一句,结果不是的,不知道为什么
else:
    print('dict dont have __next__ func')  # print this one
f = open('../data/123.txt')
print(f.__next__())
if iter(f) is f:
    print('TRUE')  # print this one 

 

2.列表解析

例如以下代码就是一个常见的列表解析:

L = range(10)
L = [x + 10 for x in L]  # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

在文件中readlines方法可以一次性地吧文件载入到行字符串的一个列表中,这个有效但是结尾会含有一个换行符。去掉换行符,并保存到一个列表的写法如下:

lines = [line.rsplit() for line in open('123.txt')] # rsplit反向去掉空格(换行符)

增加测试和嵌套循环的列表解析:

[x for x in range(5) if x %2 ==0]  # [0, 2, 4]
list(filter((lambda x: x % 2 ==0), range(5)))  # [0, 2, 4]

再复杂点的:

list(map((lambda x: x**2), filter((lambda x: x %2 ==0),  range(10))))
# [0, 4, 16, 36, 64]

矩阵中使用:

M = [[1,2,3],
     [4,5,6],
     [7,8,9]]

[row[1] for row in M]  # [2,5,8]

 

3.理解解析列表

优点:map调用比等效的for循环要快两倍,而列表解析往往比map调用要稍快一些。速度上的差距是来自于底层实现上,map和解析列表是在解释器中以C语言的速度来运行的,比python的for循环化代码在PVM中步进运行要快的多。

缺点:解析列表和map没有for循环逻辑清晰。

 

4.生成器

生成器函数:编写为常规的def语句,但是使用yield语句一次返回一个结果,在每个结果之间挂起和继续它们的状态。

生成器表达式类似于列表解析,但是,它们返回按需产生结果的一个对象,而不是构建一个结果列表,从语法上讲,生成器表达式就像一般的列表解析一样,但是它们是括在圆括号中而不是方括号中的。

 

由于二者都不会一次性构建一个列表,它们节省了内存空间,并且允许计算时间分散到各个结果请求。

 

4.1生成器函数: yield VS return

状态挂起

和返回一个值并退出的常规函数不同,生成器喊自动在生成值的时刻挂起并继续函数的执行。

生成器函数和常规函数之间的主要的代码不同之处在于,生成器yields一个值,而不是return一个值。yield语句挂起该函数并向调用者发送回一个值(return 则直接返回结果并结束了这个func),但是,保留足够的状态以使得函数能够从它离开的地方继续。当继续时,函数在上一个yield返回后立即继续执行。

迭代协议整合

迭代协议:可迭代的对象定义了一个__next__方法,它要么返回迭代中的下一项,或者引发一个特殊的stopIteration异常来终止迭代。一个对象的迭代器用iter内置函数接受。

实现方法:如果函数中包含了yield 语句(注意是语句不是函数),该语句则会编制为生成器。即自动实现迭代器协议。当调用生成器的时候,它返回一个迭代器对象,该对象支持用一个名为__next__的自动创建的方法来继续执行的接口。生成器喊也可能有一条return语句,总是在def语句块的末尾,直接终止值的生成。从技术上讲,可以在任何常规函数退出执行以后,引发一个stopIteration异常来实现。

生成器函数应用

def a(n):
    for i in range(n):
        yield i ** 2

for i in a(5):
    print(i, end=':')  # 0:1:4:9:16:

x = a(5)
print(type(x))  # generator
print(x.__next__())  # 0
print(next(x))  # 1
print(next(x))  # 4

扩展生成器函数协议:send和next

再更新

 

posted @ 2019-09-02 18:19  SsoZh  阅读(342)  评论(0编辑  收藏  举报