将分配分成两行仍然有效吗?

Is splitting assignment into two lines still just as efficient?

从运行时效率的角度来看,python 这些是否同样有效?

x = foo()
x = bar(x)

VS

x = bar(foo())

我有一个更复杂的问题,基本上可以归结为这个问题:显然,从代码长度的角度来看,第二种效率更高,但运行时是否也更好?如果不是,为什么不呢?

这是一个比较:

第一种情况:

%%timeit
def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():
    x = foo()
    y = bar(x)
    return y

test()
#Output:
'foobar'
529 ns ± 114 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

第二种情况:

%%timeit

def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():   
    x = bar(foo())
    return x

test()
#Output:
'foobar'
447 ns ± 34.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

但这只是每个案例的比较 运行%%timeit 一次。以下是每种情况下 20 次迭代的时间(以 ns 为单位的时间):

df = pd.DataFrame({'First Case(time in ns)': [623,828,634,668,715,659,703,687,614,623,697,634,686,822,671,894,752,742,721,742], 
               'Second Case(time in ns)': [901,786,686,670,677,683,685,638,628,670,695,657,698,707,726,796,868,703,609,852]})

df.plot(kind='density', figsize=(8,8))

据观察,随着每次迭代,差异都在缩小。此图显示 性能差异并不显着 。从可读性的角度来看,第二种情况看起来更好。

在第一种情况下,计算了两个表达式:第一个表达式首先将 foo() 中的 return 值赋给 x,然后第二个表达式调用 bar()在那个值上。这增加了一些开销。在第二种情况下,只评估一个表达式,同时调用两个函数并 returning 值。

它很重要微小位,但没有意义。 仅在一个测试中对函数的定义进行计时,因此必须在第一个测试中做更多的工作,从而扭曲结果。经过适当的测试,结果仅存在细微差别。使用相同的 ipython %%timeit 魔法(IPython 7.3.0 版,CPython 3.7.2 版用于 Linux x86-64),但从每个循环测试:

>>> def foo():
...     return "foo"
... def bar(text):
...     return text + "bar"
... def inline():
...     x = bar(foo())
...     return x
... def outofline():
...     x = foo()
...     x = bar(x)
...     return x
...

>>> %%timeit -r5 test = inline
... test()
...
...
332 ns ± 1.01 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)


>>> %%timeit -r5 test = outofline
... test()
...
...
341 ns ± 5.62 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

inline 代码速度更快,但差异不到 10 ns/3%。进一步内联(使正文只是 return bar(foo()))节省了 微小的 一点,但同样,它毫无意义。

这也是您所期望的;存储和加载函数局部名称是 CPython 解释器可以做的最便宜的事情,函数之间的唯一区别 is that outofline requires an extra STORE_FAST and LOAD_FAST (one following the other), and those instructions are implemented internally as nothing but assignment to and reading from 一个编译时确定的 C 数组中的槽,加上一个整数增量来调整引用计数。您为每个字节代码所需的 CPython 解释器开销付费,但实际工作的成本微不足道。

要点是:不要担心速度,写哪个版本的代码会更多readable/maintainable。在这种情况下,所有的名字都是垃圾,但是如果 foo 的输出可以被赋予一个有用的名称,然后传递给 bar,后者的输出被赋予一个不同的有用名称,如果没有这些名称,foobar 不明显,不要内联。如果关系很明显,并且 foo 的输出没有从命名中获益,则将其内联。避免从局部变量存储和加载是最微观的微优化;在几乎任何情况下,它都不会成为有意义的 性能损失的原因,因此不要将代码设计决策基于它。