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

Python 由函数创建的所有生成器共享值的最佳方式是什么?,python,Python,我问了一个关于itertools模块中函数的问题 它的代码是: 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): y

我问了一个关于
itertools
模块中函数的问题

它的代码是:

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等价物中的文档中似乎存在错误。错误在于实际函数执行了,而上述等效函数没有执行 传播作为函数参数发送的生成器内部引发的异常

@agf解决了这个问题,并提供了纯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是一个实现延迟求值的生成器。因此,只有当使用
chain
函数创建的迭代器实际需要时,才会返回
counter()

在我的代码中,我添加了一个
计数器
,它包含一个值的列表
[0]
。这样做的原因是将一个
mutable
对象放在一个可以被所有返回的迭代器
ret()
访问和更改的位置。我发现唯一合适的地方是
sentinel
功能

如果我把它放在
sentinel
函数中,那么
计数器将在每次调用
sentinel
时分配给
[0]
,这将是所有
ret()
的不同列表:

我试图将其置于
sentinel
功能之外:

counter = 0
def sentinel(fillvalue = fillvalue):
    def ret():
        counter += 1
        if counter == len(args):
            raise LongestExhausted
        yield fillvalue
    return ret()
但出现了异常:
UnboundLocalError:赋值前引用的局部变量“counter”

我添加了
global
关键字,但没有任何帮助(我认为这是因为
counter
确实不在
global
范围内):

所以,我的问题是:


在这种情况下,我使用的方法(将
mutable
list
counter=[0]
放在
函数默认值上)是最好的,还是有更好的方法来解决这个问题?

这已经以多种形式被多次询问。阅读关于和新的Python3的任何其他问题。在Python 2上,您可以使用:

或者在
ret
内部和
izip_longest
内部使用
global
,以便始终引用全局变量:

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_最长的
——请参见另一个答案上的注释

每次调用
sentinel
时(每个迭代器一次),您还定义了一个新的
ret
——您可以执行如下操作

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_最长
之外的示例代码添加到评论中的问题中:

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中访问外部作用域的问题。

使用全局是个坏主意,IMHO。您需要确保在通话之间正确重置计数器。但更严重的是,这是一个发电机;您甚至不需要线程来同时对正在运行的生成器进行多个调用,这将破坏任何理智地使用全局跟踪状态的尝试


您可以显式地将对可变对象的引用传递到sentinel,然后再传递到ret。看起来您的代码控制了对它们的所有调用。函数参数是在作用域之间传递引用的原始而乏味的方式

这一问题已以多种形式多次提出。阅读关于可变默认参数和新Python 3
非本地
关键字的任何其他问题。在上一个代码片段中,
计数器
是本地的
ret
,而不是
sentinel
,因此如果您想使用全局(即模块级)
计数器
,您需要将
global
声明放在
ret
中。(我还没有完全理解你的其余问题;我只是回答你的
UnboundLocalError
)谢谢。将
global
放入
ret
izip_longest
中确实有效!那么我把它放在函数默认值中的方法呢?这样做可以吗?谢谢你指出
ret
应该放在
sentinel
之外。我认为这是一个非常好的建议。@ovgo它的工作原理当然是“OK”,但将可变默认参数用作容器并不被认为是好的编码风格。如果您只是使用默认值来模拟函数属性(您就是这样),那么只需使用真实的函数属性即可。#1:将其视为类与类实例的对比。
def
行和任何函数属性都类似于类本身,而函数体类似于类实例。因此,任何并行存在的函数体都引用相同的默认参数和相同的函数名/属性#2:是的。由于您从未在
sentinel
中分配给
计数器
(仅分配给
计数器
的第一项),它将自动在外部范围内搜索名称,并将看到在
izip\u
中创建的
计数器。我将在我的答案中添加这方面的示例代码。你能详细说明一下“在呼叫之间重置计数器”和“不需要线程”吗?我不明白。如果我写
a=izip_longest(…);用(a)做东西;b=izip_最长(…);你是否用b()填充?全局计数器需要在
izip_longest
末尾重置,否则当我调用
izip_longest
获取b时,它不会从零开始。但这并不能解决问题,因为我也可以做
a=izip_longest(…);b=izip_最长(…);用a和b()填充东西。
。现在你有两个电话打给将军
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 counter
counter = 0
arglen = len(args)

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

def sentinel():
    yield ret()
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