我希望只从生成器中产生需要的项目数。
在下面的代码中
a, b, c = itertools.count()
我收到这个异常:
ValueError: too many values to unpack
我已经看到了几个相关问题,但是我对生成器中剩余的元素兴趣为零,我只希望收到我要求的数量,无需提前提供数量。
在我看来,Python 会确定您想要的项目数量,但随后会继续尝试读取和存储超过该数量的项目(生成 ValueError)。
我怎样才能只产生我需要的元素,而不传递我想要的元素数量?
更新0
为了帮助理解我认为应该可能的大致行为,这里有一个代码示例:
def unpack_seq(ctx, items, seq):
for name in items:
setattr(ctx, name, seq.next())
import itertools, sys
unpack_seq(sys.modules[__name__], ["a", "b", "c"], itertools.count())
print a, b, c
如果您可以改进此代码,请这样做。
Alex Martelli's answer向我建议字节操作 UNPACK_SEQUENCE 负责限制。我不明白为什么这个操作应该要求生成的序列的长度也必须完全匹配。
请注意,Python 3 具有不同的解包语法,这可能会使该问题中的技术讨论无效。
请您参考如下方法:
你需要一个深bytecode hack - 您的请求无法在 Python 源代码级别完成,但是(如果您愿意提交特定版本和 Python 发行版)可以通过在 Python 编译后对字节码进行后处理来实现。考虑一下,例如:
>>> def f(someit):
... a, b, c = someit()
...
>>> dis.dis(f)
2 0 LOAD_FAST 0 (someit)
3 CALL_FUNCTION 0
6 UNPACK_SEQUENCE 3
9 STORE_FAST 1 (a)
12 STORE_FAST 2 (b)
15 STORE_FAST 3 (c)
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
>>>
如您所见,UNPACK_SEQUENCE 3 字节码出现时没有给迭代器 someit 最少的指示(迭代器已经被调用了!)——所以你必须在字节码中为它加上“正好获取 N 个字节”操作前缀,例如:
>>> def g(someit):
... a, b, c = limit(someit(), 3)
...
>>> dis.dis(g)
2 0 LOAD_GLOBAL 0 (limit)
3 LOAD_FAST 0 (someit)
6 CALL_FUNCTION 0
9 LOAD_CONST 1 (3)
12 CALL_FUNCTION 2
15 UNPACK_SEQUENCE 3
18 STORE_FAST 1 (a)
21 STORE_FAST 2 (b)
24 STORE_FAST 3 (c)
27 LOAD_CONST 0 (None)
30 RETURN_VALUE
其中 limit 是您自己的函数,易于实现(例如通过 itertools.slice)。所以,原来的 2 字节码“快速加载;调用函数”序列(就在 unpack-sequence 字节码之前)必须变成这种 5 字节码序列,前面有 limit 的 load-global 字节码原始序列和 load-const;在它之后调用函数序列。
您当然可以在装饰器中实现此字节码修改。
或者(为了一般性)您可以改变函数的原始来源,例如通过 AST 的解析和更改,并重新编译为字节码(当然,这确实需要在装饰时提供源代码)。
这值得用于生产吗?绝对不是,当然——一个微小的“语法糖”改进需要大量的工作。然而,掌握字节码破解、ast 破解和其他你可能永远不需要的黑魔法技巧可能是一个有启发性的项目,但知道什么时候你想超越纯粹的语言向导的语言肯定很酷世界级大师的那些 - 事实上,我怀疑那些有动力成为大师的人通常是那些不能不屈服于这种“语言内部机制”的魅力的人......以及那些实际上 达到那个崇高水平的子集足够聪明,可以意识到这些努力“只是玩”,并将其作为业余事件来追求(在心理上等同于举重,很像数独或填字游戏; -) 不让他们干扰重要任务(通过部署可靠、清晰、简单、性能良好、经过良好测试、文档齐全的代码为用户提供值(value),通常没有甚至是丝毫提示对它的黑魔法;-).
