乐趣区

关于python:翻译实用的Python编程0602Customizingiteration

目录 | 上一节 (6.1 迭代协定) | [下一节 (6.3 生产者 / 消费者)]()

6.2 自定义迭代

本节探索如何应用生成器函数自定义迭代。

问题

假如你想要自定义迭代模式。

例如:倒数:

>>> for x in countdown(10):
...   print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

有一个 j 简略办法能够做到这一点。

生成器

生成器(generator)是定义了迭代的函数:

def countdown(n):
    while n > 0:
        yield n
        n -= 1

示例:

>>> for x in countdown(10):
...   print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

任何应用了 yield 语句的函数称为生成器。

生成器函数的行为不同于一般于一般函数。调用生成器函数会创立一个生成器对象(generator object),而不是立刻执行函数:

def countdown(n):
    # Added a print statement
    print('Counting down from', n)
    while n > 0:
        yield n
        n -= 1
>>> x = countdown(10)
# There is NO PRINT STATEMENT
>>> x
# x is a generator object
<generator object at 0x58490>
>>>

生成器函数只在 __next__() 办法被调用时才执行:

>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>

yield 生成一个值,然而挂起(suspend)函数执行。生成器函数会在下次调用 __next__() 办法时复原(resume),

>>> x.__next__()
9
>>> x.__next__()
8

当生成器返回最初一个值后,再次迭代将会触发一个谬误(译注:StopIteration)。

>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in ? StopIteration
>>>

察看:生成器函数实现的协定与 for 语句在列表、元组、字典、文件上应用的底层协定雷同。

练习

练习 6.4:一个简略的生成器

如果想要自定义迭代,那么你应该始终思考生成器函数。生成器函数非常容易编写——创立一个函数,执行所需的迭代逻辑,并应用 yield 发送一个值。

例如,创立一个在文件各行中查找匹配子串的生成器:

>>> def filematch(filename, substr):
        with open(filename, 'r') as f:
            for line in f:
                if substr in line:
                    yield line

>>> for line in open('Data/portfolio.csv'):
        print(line, end='')

name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>> for line in filematch('Data/portfolio.csv', 'IBM'):
        print(line, end='')"IBM",50,91.10"IBM",100,70.44
>>>

这是一种乏味的思维——你能够在函数中暗藏自定义的处理过程,并将该函数利用于 for 循环。下一个例子探索一种更不寻常的状况。

练习 6.5:监督流数据源

生成器可利用于监督实时数据源(如:日志文件,股票市场音讯)。本局部,咱们将对“应用生成器监督实时数据源”这一思维进行摸索。首先,请严格遵循以下阐明。

Data/stocksim.py 用来模拟股市数据,将实时数据一直地写入到 Data/stocklog.csv 文件。请关上一个独立的命令行窗口,进入到 Data/ 目录,而后运行 stocksim.py 程序:

bash % python3 stocksim.py

如果你应用的是 Windows 零碎,那么请找到 stocksim.py 文件,而后双击该文件运行。而后,让咱们先把这个程序放到一边(让它始终在那运行),关上另外一个命令行窗口,查看正在被模拟程序(译注:stocksim.py)写入数据的 Data/stocklog.csv 文件(译注:如果应用的是 Linux 零碎,那么能够进入到 Data 目录下,而后应用 tail -f stocklog.csv 命令查看)。你应该能够看到每隔几秒新的文本行被增加到 Data/stocklog.csv 文件中。同样,让程序在后盾运行——该程序会运行几个小时(对此不必放心)。

stocksim.py 程序运行后,让咱们编写一个程序来关上 Data/stocklog.csv 文件、挪动到文件开端、并查看新的输入。请在 Work 目录下创立 follow.py 文件,并把以下代码放入其中:

# follow.py
import os
import time

f = open('Data/stocklog.csv')
f.seek(0, os.SEEK_END)   # Move file pointer 0 bytes from end of file

while True:
    line = f.readline()
    if line == '':
        time.sleep(0.1)   # Sleep briefly and retry
        continue
    fields = line.split(',')
    name = fields[0].strip('"')
    price = float(fields[1])
    change = float(fields[4])
    if change < 0:
        print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

运行 follow.py 程序,你将会看到实时的股票报价(stock ticker)。follow.py 里的代码相似于 Unix 零碎查看日志文件的 tail -f 命令。

注意事项:在本示例中,readline() 办法的应用与通常从文件中读取行的形式略微有点不同(通常应用 for 循环)。在这种状况下,咱们应用 readline() 来反复探测文件的开端,以查看是否增加了新的数据(readline() 办法返回新的数据或者空字符串)。

练习 6.6:应用生成器生成数据

查看练习 6.5 中代码你会发现,代码的第一局部产生了几行数据,而 while 循环开端的语句生产数据。生成器的一个次要个性是你能够将生成数据的代码移动到可重用的函数中。

请批改练习 6.5 的代码,以便通过生成器函数 follow(filename) 执行文件读取。请实现更改以便上面的代码可能工作:

>>> for line in follow('Data/stocklog.csv'):
          print(line, end='')

... Should see lines of output produced here ...

请批改股票报价代码,使代码看起来像上面这样:

if __name__ == '__main__':
    for line in follow('Data/stocklog.csv'):
        fields = line.split(',')
        name = fields[0].strip('"')
        price = float(fields[1])
        change = float(fields[4])
        if change < 0:
            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

练习 6.7:查看股票投资组合

请批改 follow.py 程序,以便程序可能查看股票数据流,并打印股票投资组合中的那些股票的信息。示例:

if __name__ == '__main__':
    import report

    portfolio = report.read_portfolio('Data/portfolio.csv')

    for line in follow('Data/stocklog.csv'):
        fields = line.split(',')
        name = fields[0].strip('"')
        price = float(fields[1])
        change = float(fields[4])
        if name in portfolio:
            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

注意事项:要想这段代码可能运行,Portfolio 类必须反对 in 运算符。请参阅 练习 6.3,确保 Portfolio 类实现了 __contains__() 运算符。

探讨

在这里,你将一个乏味的迭代模式(在文件开端读取行)挪动到函数中。follow() 函数当初是能够在任何程序中应用的齐全通用的实用程序。例如,你能够应用 follow() 函数查看服务器日志、调试日志、其它相似的数据源。

目录 | 上一节 (6.1 迭代协定) | [下一节 (6.3 生产者 / 消费者)]()

注:残缺翻译见 https://github.com/codists/practical-python-zh

退出移动版