编写高质量Python (第30条) 不要让函数直接返回列表,应该让它逐个生成列表里的值

第 30 条 不要让函数直接返回列表,应该让它逐个生成列表里的值

​ 如果函数要返回的是个包含许多结果的序列,那么最简单的办法是把这戏结果放到列表中。例如,我们要返回字符串里每个单词的首字母所对应的下标。下面这种写法,会把每次遇到的新单词所在的位置追加到存放结果的 result 列表中,在函数末尾返回这个列表。

def index_words(text):
    result = []
    if text:
        result.append(0)
    for index, letter in enumerate(text):
        if letter == ' ':
            result.append(index + 1)

    return result

​ 我们把一段实例文档传给这个函数,它可以返回正确的结果。

address = 'Four score and seven years ago...'
result = index_words(address)
print(result[:10])

>>>
[0, 5, 11, 15, 21, 27]

​ index_words 函数有两个缺点。

​ 第一个缺点是,它的代码看起来有点杂乱。每找到一个新单词,它都要调用 append 方法,而调用这个方法时,必须写上 result.append 这样一串字符串,这就把我们想要强调的重点,也就是这个新单词在字符串中的位置(index + 1)淡化了。另外,函数还必须专门用一行代码创建这个保存结果的 result 列表,并且要用一条 return 语句把它返回给调用者。这样算下来,虽然函数的主体部分大约有 130 个字符(非空白的),但真正重要的大约在 75 个左右。

​ 这种函数改用 生成器来实现会比较好。生成器由包含 yield 表达式的函数创建。下面就定义一个生成器函数,实现与刚才那个函数相同的效果。

def index_words(text):
    if text:
        yield 0
    for index, letter in enumerate(text):
        if letter == ' ':
            yield index + 1

​ 调用生成器函数并不会让其中的代码立刻得到执行,它会返回一个迭代器。把这个迭代器传给 Python 内置函数的 next 函数,就可以把生成器函数推进到它的下一条表达式。生成器会把 yield 表达式的值通过迭代器返回给调用者。

address = 'Four score and seven years ago...'
it = index_words(address)
print(next(it))
print(next(it))

>>>
0
5

​ 这次的 index_words_list 函数,比刚才的那个函数好懂的多,因为它把设计到列表的操作全部都简化掉了。它通过 yield 表达式来传递结果,而不像刚才那样,要把结果返回到列表之中。如果确实要制作一份列表,那可以把生成器函数返回到迭代器传递内置函数(原理参见第 32 条)。

address = 'Four score and seven years ago...'
it = list(index_words(address))
print(it[:10])

>>>
[0, 5, 11, 15, 21, 27]

​ index_words 函数的第二个缺点是,它必须把所有结果保存到列表中,然后才能返回列表。如果输入到数据特别多,那么程序可能会因为耗尽内存而崩溃。

​ 相反,采用生成器函数来实现,就不会有那么多问题。它可以接受长度任意多输入信息,并把内存消耗量压得比较低。例如下面这个生成器,只需要把当前这行文字从文件度进来就行,每次推进的时候,它都只处理一个单词,直到把这行文字处理完毕,才读入下一行文字。

import itertools
def index_file(handle):
    offset = 0
    for line in handle:
        if line:
            yield offset
        for letter in line:
            offset += 1
            if letter == ' ':
                yield offset
                
    

​ 该函数运行时所消耗的内存,取决于文件中最长的那一行包含的字符数。把刚才那份输入数据存放在 address.txt 文件,让这个函数去读取并用它返回的生成器构建一份列表,可以看到跟原来相同的效果(islice 函数的详细用法,参见第 36 条)。

with open ('address.txt', 'r') as f:
    it = index_file(f)
    results = itertools.islice(it, 0 ,10)
    print(list(results))
    
>>>
[0, 5, 11, 15, 21, 27, 31, 35, 43, 51]

​ 定义这种生成器函数的时候,只有一个地方需要注意,就是调用者无法重复使用函数所返回的迭代器,因为这些迭代器是有状态的(参见 第 31 条)。

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值