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}})是最好的,还是有更好的方法来解决这个问题?
答案 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。看起来您的代码控制着对它们的所有调用。函数参数是在范围之间传递引用的原始和无聊的方式!