【问题标题】:How to wrap or embed generators?如何包装或嵌入生成器?
【发布时间】:2017-11-24 23:30:00
【问题描述】:

我正在尝试提供一个统一的接口,用于从单个目录或目录列表中检索所有文件。

def get_files(dir_or_dirs):
    def helper(indir):
        file_list = glob.glob("*.txt")
        for file in file_list:
            yield file

    if type(dir_or_dirs) is list:
        # a list of source dirs
        for dir in dir_or_dirs:
            yield helper(dir)
    else:
        # a single source dir
        yield helper(dir_or_dirs)

def print_all_files(file_iter):
    for file in file_iter:
        print(file)        # error here!

问题:

  1. 错误表明“文件”仍然是一个生成器,无论输入是单个目录还是它的列表。为什么它仍然是发电机?
  2. 是否可以在函数中包装或嵌入生成器?如果是这样,如何使这项工作?

【问题讨论】:

  • 你为什么要yieldhelper的返回值? Helper 是一个生成器函数,它返回一个生成器迭代器。如果你想产生生成器产生的所有东西,那就是yield from
  • 正在尝试为所有目录下的所有文件获取生成器。感谢'yield from'的提醒!

标签: python generator yield


【解决方案1】:

你每次都让helper()

yield helper(dir)

helper() 本身就是一个生成器。

在 Python 3.3 及更高版本中,请改用 yield from

yield from helper(dir)

这个委派控制权给另一个生成器。来自Yield expressions 文档:

当使用yield from <expr> 时,它将提供的表达式视为子迭代器。该子迭代器生成的所有值都直接传递给当前生成器方法的调用者。

在较旧的 Python 版本中,包括 Python 2.x,使用另一个循环:

for file in helper(dir):
    yield file

有关yield from 作用的更多信息,请参阅PEP 380 -- Syntax for Delegating to a Subgenerator

并不是你真的需要辅助函数,它只是循环glob.glob() 结果而已,你可以直接做到这一点。

您还需要更正您的函数以实际使用indir;目前您正在忽略该参数,因此您只能从 当前工作目录 获取文本文件。

接下来,您想使用glob.iglob() 而不是glob.glob() 来获得对os.scandir() 的惰性求值,而不是一次将所有结果加载到内存中。我只是将一个非列表 dir_or_dirs 值变成一个列表,然后只使用一个循环:

import glob
import os.path

def get_files(dirs):
    if not isinstance(dirs, list):
        # make it a list with one element
        dirs = [dirs]

    for dir in dirs:
        pattern = os.path.join(dir, '*.txt')
        yield from glob.iglob(pattern)

现在,我将使用可变数量的参数代替字符串或列表的单个参数,使用 *args 参数语法:

def get_files(*dirs):
    for dir in dirs:
        pattern = os.path.join(dir, '*.txt')
        yield from glob.iglob(pattern)

这可以用 0 个或多个目录调用:

for file in get_files('/path/to/foo', '/path/to/bar'):
    # ...

【讨论】:

  • 如果你只是想知道你是否可以遍历某些东西,isinstance(dirs, collections.abc.Iterable) 是一个更好的检查,因为它适用于任何可迭代的,而不仅仅是列表。一种更 Pythonic 的方式可能是将 dirs 作为 vararg:def get_files(*dirs),然后调用者可以使用单个参数、varargs 或 get_dirs(*iterable_argument) 的列表进行调用。
  • @zstewart:不,使用collections.abc.Iterable 不是更好的检查,因为字符串也是可迭代的。然后将单个目录视为单独的单个字符。
  • 谢谢! py2.7 提示有效!我也会尝试使用 py3。也感谢您提供的插图和进一步提高 Python 代码质量的提示!
  • @MartijnPieters 哦,对。呃。我个人仍然会选择适用于任何可迭代的解决方案。 *dirs 可能是最好的选择,因为它保留了单个参数和可迭代参数的开放式鸭子类型。虽然这确实意味着任何可迭代的参数都会被热切地评估。另一种选择是显式检查os.path.joinstrbytes 和 3.6+ 中的os.PathLike)接受的参数类型,并且只将它们转换为新的单元素列表/元组。然后get_files 将能够懒惰地评估传递给它的惰性迭代器。
  • @zstewart:此时您只需放下脚,让 API 只接受一个可迭代的目录,并避免使用鸭式打字。
猜你喜欢
  • 1970-01-01
  • 2018-10-08
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多