代码之家  ›  专栏  ›  技术社区  ›  Marc

ITertools或手写生成器-什么更好?

  •  7
  • Marc  · 技术社区  · 14 年前

    我有许多Python生成器,我想将它们组合成一个新的生成器。我可以很容易地通过一个手写的生成器使用一堆 yield 声明。

    另一方面, itertools 模块是为类似这样的事情而设计的,在我看来,创建生成器的蟒蛇式方法似乎是将它的各种迭代器插入到一起。 迭代工具 模块。

    然而,在目前的问题中,它很快变得相当复杂(发电机需要保持某种状态,例如,第一个或之后的项目是否正在处理中),第i个输出进一步取决于第i个输入项目的条件,并且在将各种输入列表加入通用电气之前,必须对它们进行不同的处理。系留列表

    由于标准迭代器的组合可以解决我的问题——由于源代码的一维特性——几乎不可理解,我想知道使用标准是否有任何好处 迭代工具 生成器与手写生成器函数(在基本和更高级的情况下)。实际上,我认为在90%的情况下,手写版本更容易阅读——可能是因为与链接迭代器的功能风格相比,手写版本更具有命令式风格。

    编辑

    为了说明我的问题,这里有一个(玩具)例子:让 a b 是两个相同长度的iterable(输入数据)。项目 包含整数,项 是ITerables本身,其单个项是字符串。输出应与以下发电机功能的输出相对应:

    from itertools import *
    def generator(a, b):
        first = True
        for i, s in izip(a, b):
            if first:
                yield "First line"
                first = False
            else:
                yield "Some later line"
            if i == 0:
                yield "The parameter vanishes."
            else:
                yield "The parameter is:"
                yield i
            yield "The strings are:"
            comma = False
            for t in s:
                if comma:
                    yield ','
                else:
                    comma = True
                yield t
    

    如果我使用生成器表达式和 迭代工具 模块,我最终得到如下的结果:

    from itertools import *
    def generator2(a, b):
        return (z for i, s, c in izip(a, b, count())
                for y in (("First line" if c == 0 else "Some later line",),
                          ("The parameter vanishes.",) if i == 0
                          else ("The parameter is:", i),
                          ("The strings are:",),
                          islice((x for t in s for x in (',', t)), 1, None))
                for z in y)
    

    例子

    >>> a = (1, 0, 2), ("ab", "cd", "ef")
    >>> print([x for x in generator(a, b)])
    ['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f']
    >>> print([x for x in generator2(a, b)])
    ['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f']
    

    这可能比我的第一个解决方案更优雅,但看起来像是一次写一次不理解以后的代码。我想知道这种编写生成器的方法是否有足够的优势,人们应该这样做。

    P.S.:我想我对函数解决方案的部分问题是,为了尽量减少python中的关键字数量,一些关键字(如“for”、“if”和“else”)已被回收用于表达式中,以便它们在表达式中的位置变得习惯(生成器表达式中的顺序 z for x in a for y in x for z in y 至少对我来说,看起来不像经典的那种 for 循环: for x in a: for y in x: for z in y: yield z )

    1 回复  |  直到 14 年前
        1
  •  7
  •   aaronasterling    14 年前

    $ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator1(a, b))'
    10 loops, best of 3: 169 msec per loop
    
    $ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator2(a, b))'
    10 loops, best of 3: 489 msec per loop
    
    $ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator3(a, b))'
    10 loops, best of 3: 385 msec per loop
    

    def generator3(parameters, strings):
        # replace strings with a generator of generators for the individual charachters
        strings = (it.islice((char for string_char in string_ for char in (',', string_char)), 1, None)
                   for string_ in strings)
    
        # interpolate strings with the notices
        strings = (it.chain(('The strings are:',), string_) for string_ in strings)
    
        # nest them in tuples so they're ate the same level as the other generators
        separators = it.chain((('First line',),), it.cycle((('Some later line',),)))
    
        # replace the parameters with the appropriate tuples
        parameters = (('The parameter is:', p) if p else ('The parameter vanishes.',)
                      for p in parameters)
    
        # combine the separators, parameters and strings
        output = it.izip(separators, parameters, strings)
    
        # flatten it twice and return it
        output = it.chain.from_iterable(output)
        return it.chain.from_iterable(output)   
    

    def make_test_case():
        a = [i % 100 for i in range(10000)]
        b = [('12345'*10)[:(i%50)+1] for i in range(10000)]
        return a, b