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
listcounter=[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