仅按需从生成器中生成值

1 投票
4 回答
716 浏览
提问于 2025-04-16 00:08

我希望从一个生成器中只获取所需数量的项目。

在下面的代码中

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 的回答让我觉得字节操作 UNPACK_SEQUENCE 可能是限制的原因。我不明白为什么这个操作要求生成的序列长度也必须完全匹配。

请注意,Python 3 有不同的解包语法,这可能使得这个问题的技术讨论变得无效。

4 个回答

3

Python的工作方式可能和你想的不一样。在任何赋值操作中,比如说

a, b, c = itertools.count()

右边的内容会先被计算出来,之后才是左边的部分。右边的内容不知道左边有多少个东西,除非你告诉它。

6

你需要确保两边的项目数量是相等的。一个方法是使用 itertools模块中的 islice

from itertools import islice
a, b, c = islice(itertools.count(), 3)
6

你需要进行深层的字节码黑客操作——你想要的事情在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个字节码“加载快速;调用函数”序列(在解包序列字节码之前)必须变成这种5个字节码的序列,在原始序列之前加上一个加载全局的字节码来获取limit,然后在它之后是load-const; call function的序列。

当然,你可以在一个装饰器中实现这个字节码的修改。

另外(为了通用性),你也可以考虑修改函数的原始源代码,比如通过解析和修改抽象语法树(AST),然后重新编译成字节码(但这当然需要在装饰时源代码是可用的)。

这样做在生产环境中值得吗?当然不值得——为了一个微不足道的“语法糖”改进而付出如此巨大的工作量,实在是太荒谬了。不过,这可以是一个很有教育意义的项目,帮助你掌握字节码黑客、AST黑客和其他一些黑科技技巧,虽然你可能永远用不上,但知道这些技巧确实很酷。当你想要从一个普通的编程者成长为世界级的高手时,这些知识会让你受益匪浅——我怀疑那些想成为高手的人,通常都是对这种“语言内部机制”充满好奇的人……而那些真正能达到那种高水平的人,往往是那些明白这些努力只是“玩乐”的人,他们把这些当作业余活动来追求(就像举重一样的思维锻炼,类似于数独或填字游戏;-)),而不会让它们干扰到重要的任务(为用户提供价值,部署稳固、清晰、简单、性能良好、经过充分测试和良好文档的代码,通常完全不需要任何黑魔法的痕迹;-)。

撰写回答