由函数创建的所有生成器共享值的最佳方法是什么?

时间:2011-09-12 22:12:28

标签: python

Here我在itertools模块中询问了izip_longest函数的问题。

代码:

def izip_longest_from_docs(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')
    def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
        yield counter()         # yields the fillvalue, or raises IndexError
    fillers = repeat(fillvalue)
    iters = [chain(it, sentinel(), fillers) for it in args]
    try:
        for tup in izip(*iters):
            yield tup
    except IndexError:
        pass

在纯Python相当于该函数的文档中似乎存在错误。错误是真正的功能和上面提到的等价物没有 传播在作为函数参数发送的生成器内引发的IndexError异常。

@agf解决了问题,gave解决了纯Python等效的修正版本。

但在他撰写解决方案的同时,我也做了自己的解决方案。在制作它的同时,我遇到了一个问题,我希望通过提出这个问题来解开。

我想出的代码是:

def izip_longest_modified_my(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')

    class LongestExhausted(Exception):
        pass

    def sentinel(fillvalue = fillvalue, counter = [0]):
        def ret():
            counter[0] += 1
            if counter[0] == len(args):
                raise LongestExhausted
            yield fillvalue
        return ret()

    fillers = repeat(fillvalue)
    iters = [chain(it, sentinel(), fillers) for it in args]
    try:
        for tup in izip(*iters):
            yield tup
    except LongestExhausted:
        pass 

在原始代码中sentinel是一个实现延迟评估的生成器。因此只有在使用counter()函数创建的迭代器实际需要chain时才会返回counter

在我的代码中,我添加了[0],其中包含一个值mutable的列表。原因是将ret()对象放入某个地方,所有返回的迭代器function_defaults都可以访问它并由它们更改。我发现合适的唯一地方是sentinel sentinel

如果我将其放在counter函数中,那么[0]会在sentinel的每次调用中分配给ret(),这将是所有def sentinel(fillvalue = fillvalue): counter = [0] def ret(): counter[0] += 1 if counter[0] == len(args): raise LongestExhausted yield fillvalue return ret() S:

sentinel

我试图把它放在counter = 0 def sentinel(fillvalue = fillvalue): def ret(): counter += 1 if counter == len(args): raise LongestExhausted yield fillvalue return ret() 函数之外:

UnboundLocalError: local variable 'counter' referenced before assignment

但异常上升:global

我添加了counter个关键字,但它没有帮助(我认为因为global实际上不在counter = 0 def sentinel(fillvalue = fillvalue): global counter def ret(): counter += 1 if counter == len(args): raise LongestExhausted yield fillvalue return ret() 范围内):

mutable

所以,我的问题是

在这种情况下,我使用的方法(将counter = [0]列出function_defaults列为{{1}})是最好的,还是有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:1)

这已经多次以多种形式被问到过。阅读有关mutable default arguments和新Python 3 nonlocal keyword的任何其他问题。在Python 2上,您可以使用function attribute

def sentinel(fillvalue = fillvalue):
    def ret():
        sentinel.counter += 1
        if sentinel.counter == len(args):
            raise LongestExhausted
        yield fillvalue
    return ret()
sentinel.counter = 0

或在global内和ret内使用izip_longest,因此您始终引用全局变量:

global counter
counter = 0
def sentinel(fillvalue = fillvalue):
    def ret():
        global counter
        counter += 1
        if counter == len(args):
            raise LongestExhausted
        yield fillvalue
    return ret()

但是,使用global一次只限制一个izip_longest - 请参阅另一个答案的评论。

每次调用ret时,你也会定义一个新的sentinel(每个迭代器一次) - 你可以做一些类似的事情

global counter
counter = 0
arglen = len(args)

def ret():
    global counter
    counter += 1
    if counter == arglen:
        raise LongestExhausted
    return fillvalue

def sentinel():
    yield ret()

在评论中提出sentinel izip_longest之外的示例代码:

def sentinel(counter, arglen, fillvalue):
    def ret():
        counter[0] += 1
        if counter[0] == arglen:
            raise LongestExhausted
        yield fillvalue
    return ret()


def izip_longest_modified_my(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')

    class LongestExhausted(Exception):
        pass

    fillers = repeat(fillvalue)
    counter = [0]
    arglen = len(args)
    iters = [chain(it, sentinel(counter, arglen, fillvalue), fillers) for it in args]
    try:
        for tup in izip(*iters):
            yield tup
    except LongestExhausted:
        pass

在这里,您再次使用列表作为容器来解决在Python 2中访问外部作用域的问题。

答案 1 :(得分:1)

使用全局是一个坏主意,恕我直言。您需要确保在呼叫之间正确地重置计数器。但更严重的是这是一个发电机;你甚至不需要线程同时对飞行中的发电机进行多次调用,这将破坏任何企图使用全局来跟踪状态的企图。

您可以显式将对可变对象的引用传递给sentinel,然后再传入ret。看起来您的代码控制着对它们的所有调用。函数参数是在范围之间传递引用的原始和无聊的方式!