草庐IT

python - 生成器理解表达式之间的差异

coder 2023-08-13 原文

据我所知,通过推导式1创建生成器的方法有三种。

经典的:

def f1():
    g = (i for i in range(10))

yield 变体:

def f2():
    g = [(yield i) for i in range(10)]

yield from 变体(在函数内部引发 SyntaxError):

def f3():
    g = [(yield from range(10))]

这三个变体导致不同的字节码,这并不奇怪。 第一个是最好的似乎是合乎逻辑的,因为它是一种专用的、直接的语法,可以通过理解创建生成器。 然而,它并不是生成最短字节码的那个。

在 Python 3.6 中反汇编

经典生成器理解

>>> dis.dis(f1)
4           0 LOAD_CONST               1 (<code object <genexpr> at...>)
            2 LOAD_CONST               2 ('f1.<locals>.<genexpr>')
            4 MAKE_FUNCTION            0
            6 LOAD_GLOBAL              0 (range)
            8 LOAD_CONST               3 (10)
           10 CALL_FUNCTION            1
           12 GET_ITER
           14 CALL_FUNCTION            1
           16 STORE_FAST               0 (g)

5          18 LOAD_FAST                0 (g)
           20 RETURN_VALUE

yield 变体

>>> dis.dis(f2)
8           0 LOAD_CONST               1 (<code object <listcomp> at...>)
            2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
            4 MAKE_FUNCTION            0
            6 LOAD_GLOBAL              0 (range)
            8 LOAD_CONST               3 (10)
           10 CALL_FUNCTION            1
           12 GET_ITER
           14 CALL_FUNCTION            1
           16 STORE_FAST               0 (g)

9          18 LOAD_FAST                0 (g)
           20 RETURN_VALUE

yield 变体

>>> dis.dis(f3)
12           0 LOAD_GLOBAL              0 (range)
             2 LOAD_CONST               1 (10)
             4 CALL_FUNCTION            1
             6 GET_YIELD_FROM_ITER
             8 LOAD_CONST               0 (None)
            10 YIELD_FROM
            12 BUILD_LIST               1
            14 STORE_FAST               0 (g)

13          16 LOAD_FAST                0 (g)
            18 RETURN_VALUE
        

此外,timeit 比较表明,yield from 变体是最快的(仍然使用 Python 3.6 运行):

>>> timeit(f1)
0.5334039637357152

>>> timeit(f2)
0.5358906506760719

>>> timeit(f3)
0.19329123352712596

f3 大约是 f1f2 的 2.7 倍。

正如 Leon 在评论中提到的那样,生成器的效率最好通过它可以迭代的速度来衡量。 所以我更改了这三个函数,以便它们遍历生成器,并调用一个虚拟函数。

def f():
    pass

def fn():
    g = ...
    for _ in g:
        f()

结果更加明显:

>>> timeit(f1)
1.6017412817975778

>>> timeit(f2)
1.778684261368946

>>> timeit(f3)
0.1960603619517669

f3 现在是 f1 的 8.4 倍,是 f2 的 9.3 倍。

注意当可迭代不是range(10)而是静态可迭代时,结果或多或少是相同的,例如[0, 1 , 2, 3, 4, 5]。 因此,速度的差异与 range 以某种方式优化无关。


那么,这三种方式有什么区别呢? 更具体地说,yield from 变体和其他两个有什么区别?

自然构造 (elt for elt in it) 比棘手的 [(yield from it)] 慢是正常行为吗? 我是否应该从现在开始在我的所有脚本中用后者替换前者,或者使用 yield from 结构有什么缺点吗?


编辑

这都是相关的,所以我不想提出一个新问题,但这变得更加陌生了。 我尝试比较 range(10)[(yield from range(10))]

def f1():
    for i in range(10):
        print(i)
    
def f2():
    for i in [(yield from range(10))]:
        print(i)

>>> timeit(f1, number=100000)
26.715589237537195

>>> timeit(f2, number=100000)
0.019948781941049987

所以。现在,[(yield from range(10))] 的迭代速度是 range(10) 迭代的 186 倍?

您如何解释为什么遍历 [(yield from range(10))] 比遍历 range(10) 快得多?


1:对于持怀疑态度的人,后面的三个表达式确实生成了一个 generator 对象;尝试对它们调用 type

最佳答案

这是你应该做的:

g = (i for i in range(10))

这是一个生成器表达式。相当于

def temp(outer):
    for i in outer:
        yield i
g = temp(range(10))

但是如果你只是想要一个包含 range(10) 元素的迭代器,你可以这样做

g = range(10)

您不需要将其中的任何内容包装在函数中。

如果您是来这里学习编写什么代码的,您可以停止阅读。这篇文章的其余部分是一个很长的技术解释,解释为什么其他代码片段被破坏并且不应该被使用,包括解释为什么你的计时也被破坏。


这个:

g = [(yield i) for i in range(10)]

是一个损坏的结构,应该在几年前就被移除。 8年后问题是originally reported ,删除它的过程是 finally beginning .不要这样做。

虽然它仍在语言中,但在 Python 3 上,它等同于

def temp(outer):
    l = []
    for i in outer:
        l.append((yield i))
    return l
g = temp(range(10))

列表理解应该返回列表,但由于 yield,这个不会。它的行为有点像生成器表达式,它产生的结果与您的第一个片段相同,但它构建了一个不必要的列表并将其附加到最后引发的 StopIteration

>>> g = [(yield i) for i in range(10)]
>>> [next(g) for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: [None, None, None, None, None, None, None, None, None, None]

这很困惑并且浪费内存。不要这样做。 (如果您想知道所有这些 None 的来源,请阅读 PEP 342。)

在 Python 2 上,g = [(yield i) for i in range(10)] 做了完全不同的事情。 Python 2 不给列表推导式它们自己的范围——特别是列表推导式,而不是字典或集合推导式——所以 yield 由包含此行的任何函数执行。在 Python 2 上,这个:

def f():
    g = [(yield i) for i in range(10)]

相当于

def f():
    temp = []
    for i in range(10):
        temp.append((yield i))
    g = temp

使 f 成为基于生成器的协程,在 pre-async sense 中.同样,如果您的目标是获得一个生成器,那么您已经浪费了大量时间来构建一个毫无意义的列表。


这个:

g = [(yield from range(10))]

很愚蠢,但这次没有任何责任归咎于 Python。

这里根本没有理解或 genexp。括号不是列表理解;所有工作都由 yield from 完成,然后您构建一个 1 元素列表,其中包含 yield from 的(无用)返回值。你的f3:

def f3():
    g = [(yield from range(10))]

当去掉不必要的列表构建时,简化为

def f3():
    yield from range(10)

或者,忽略 yield from 所做的所有协程支持,

def f3():
    for i in range(10):
        yield i

你的时间也坏了。

在您的第一次计时中,f1f2 创建可以在这些函数内部使用的生成器对象,尽管 f2 的生成器很奇怪. f3 不会那样做; f3 一个生成器函数。 f3 的主体不会在您的计时中运行,如果是这样,它的 g 的行为将与其他函数的 g 完全不同。实际上可以与 f1f2 相媲美的时间是

def f4():
    g = f3()

在您的第二次计时中,f2 实际上并未运行,原因与 f3 在前一次计时中被破坏的原因相同。在您的第二次计时中,f2 没有遍历生成器。相反,yield fromf2 本身变成了生成器函数。

关于python - 生成器理解表达式之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45190729/

有关python - 生成器理解表达式之间的差异的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  6. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  7. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  8. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  9. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  10. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

随机推荐