【问题标题】:Time to assign a variable in Python是时候在 Python 中分配变量了
【发布时间】:2018-12-05 08:41:39
【问题描述】:

假设我在一个非常紧密的循环中有以下内容:

a = func(x)
b = func2(a)

变量a 不在其他任何地方使用。

Python 会自动编译掉对a 的赋值,还是每次都花时间进行变量赋值?换句话说,这段代码是相同的,还是由于没有分配给a而稍微快一点?

b = func2(func(x))

Python2.7 与 Python3 的行为是否相同?

【问题讨论】:

  • 不确定,但没有分配可能会快一点
  • 这似乎是一个您可以自己尝试回答的问题(例如,使用timeit 模块)。
  • @larsks 我读到timeit 对于如此小的时间差异有些不可靠。下面所有使用它的答案都显示出纳秒级的差异,这并不令人信服,可能只是噪音。字节码答案是我一直在寻找的答案(我以前不知道dis

标签: python variables variable-assignment assign


【解决方案1】:

因此,使用非常有趣的dis 模块,我们可以查看从您提供的 python 代码生成的实际字节码。为了简单起见,我将funcfunc2 替换为内置函数(intfloat)。

所以我们的源代码如下所示:

def assign():
    a = int()
    b = float(a)

相对于简化版:

def simple():
    b = float(int())

然后从cpython 2.7解释器开始,我们可以看到assign函数生成的字节码:

dis.dis(assign)
  2           0 LOAD_GLOBAL              0 (int)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (a)

  3           9 LOAD_GLOBAL              1 (float)
             12 LOAD_FAST                0 (a)
             15 CALL_FUNCTION            1
             18 STORE_FAST               1 (b)
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE

正如您所见,没有任何窥视孔优化来删除不必要的中间变量,与简化的“简单方法”的字节码相比,这会导致额外的 2 条指令(STORE_FAST aLOAD_FAST a):

dis.dis(simple)
  2           0 LOAD_GLOBAL              0 (float)
              3 LOAD_GLOBAL              1 (int)
              6 CALL_FUNCTION            0
              9 CALL_FUNCTION            1
             12 STORE_FAST               0 (b)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

这对于 Python 3.5 的 CPython 解释器和 Python 2.7 的 pypy 解释器是相同的。

【讨论】:

    【解决方案2】:

    使用dis模块比较字节码:看起来第二种方法产生较少的操作

    import dis
    
    print(dis.dis('a=f(2);b=g(a)'))
    print(dis.dis('b=g(f(2))'))
    
    
    >>>   
      1           0 LOAD_NAME                0 (f)
                  2 LOAD_CONST               0 (2)
                  4 CALL_FUNCTION            1
                  6 STORE_NAME               1 (a)
                  8 LOAD_NAME                2 (g)
                 10 LOAD_NAME                1 (a)
                 12 CALL_FUNCTION            1
                 14 STORE_NAME               3 (b)
                 16 LOAD_CONST               1 (None)
                 18 RETURN_VALUE
    None
      1           0 LOAD_NAME                0 (g)
                  2 LOAD_NAME                1 (f)
                  4 LOAD_CONST               0 (2)
                  6 CALL_FUNCTION            1
                  8 CALL_FUNCTION            1
                 10 STORE_NAME               2 (b)
                 12 LOAD_CONST               1 (None)
                 14 RETURN_VALUE
    None
    

    【讨论】:

    • 与@MatthewStory 的答案基本相同,但显示了使用dis.dis 的不同方式 - 除非我得到一些负面回应,否则我会保留它。
    【解决方案3】:

    使用timeit 可以轻松检查此类查询。这是Python2.7 的结果。

    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
    1000000 loops, best of 3: 0.29 usec per loop
    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
    1000000 loops, best of 3: 0.284 usec per loop
    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
    1000000 loops, best of 3: 0.285 usec per loop
    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
    1000000 loops, best of 3: 0.283 usec per loop
    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
    1000000 loops, best of 3: 0.294 usec per loop
    root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
    1000000 loops, best of 3: 0.286 usec per loop
    

    这显示了与描述使用很棒的dis 模块的其他答案一致的结果。

    【讨论】:

    • f1f2 的创建放在设置块中而不是在时序循环中可能更有意义。
    • 同意。无需为每次迭代进行初始化。
    【解决方案4】:

    实际时间将取决于函数func()func2() 执行的操作。不是最好的例子,但下面给出了一个快速(和肮脏)的测试代码:

    import time
    
    def func(x):
        return 5
    
    def func2(a):
        return 10
    
    t0 = time.time()
    x = 10
    for i in range(1,10000):
        a = func(x)
        b = func2(a)
    t1 = time.time()
    
    print("Time 1: ", t1-t0)
    
    t2 = time.time()
    x = 10
    for i in range(1,10000):
        b = func2(func(x))
    t3 = time.time()
    
    print("Time 2: ", t3-t2)
    

    以上代码的输出为:

    Time 1:  0.0029211044311523438
    Time 2:  0.002785921096801758
    

    所以是的,我们避免分配 a 的实现在 Pyhton 3 中稍微快了一点。

    【讨论】:

    • 为什么要依赖具体的功能?
    • 对不起,我的意思是,实际时间将取决于功能的内容。编辑了回复。
    猜你喜欢
    • 1970-01-01
    • 2019-10-03
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多