对于第一个示例,很明显这是单引用优化的情况(实际上有两个引用:一个来自对象本身,一个来自对象本身,一个 LOAD_FAST;unicode_concatenate 将在传递控制之前尝试将其减少到 1到 PyUnicode_Append) 由 CPython 使用 unicode_modifiable 函数完成:
static int
unicode_modifiable(PyObject *unicode)
{
assert(_PyUnicode_CHECK(unicode));
if (Py_REFCNT(unicode) != 1)
return 0;
if (_PyUnicode_HASH(unicode) != -1)
return 0;
if (PyUnicode_CHECK_INTERNED(unicode))
return 0;
if (!PyUnicode_CheckExact(unicode))
return 0;
#ifdef Py_DEBUG
/* singleton refcount is greater than 1 */
assert(!unicode_is_singleton(unicode));
#endif
return 1;
}
但在第二种情况下,因为实例数据存储在 Python dict 而不是一个简单的变量中,所以情况略有不同。
a.accum_ += 'foo'
实际上需要预取a.accum_ 的值并将其存储到堆栈中。所以,现在字符串有至少三个引用:一个来自实例字典,一个来自DUP_TOP,一个来自PyObject_GetAttr,由LOAD_ATTR 使用。因此,Python 无法优化这种情况,因为就地修改其中一个也会影响其他引用。
>>> class A:
pass
...
>>> a = A()
>>> def func():
a.str = 'spam'
print a.str
return '_from_func'
...
>>> a.str = 'foo'
>>> a.str += func()
spam
您可能希望这里的输出为'spam_from_func',但它会有所不同,因为a.str 的原始值是在调用func() 之前由Python 存储的。
>>> a.str
'foo_from_func'
字节码:
>>> import dis
>>> def func_class():
a = Foo()
a.accum = ''
a.accum += 'zzzzz\n'
...
>>> dis.dis(func_class)
2 0 LOAD_GLOBAL 0 (Foo)
3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
6 STORE_FAST 0 (a)
3 9 LOAD_CONST 1 ('')
12 LOAD_FAST 0 (a)
15 STORE_ATTR 1 (accum)
4 18 LOAD_FAST 0 (a)
21 DUP_TOP
22 LOAD_ATTR 1 (accum)
25 LOAD_CONST 2 ('zzzzz\n')
28 INPLACE_ADD
29 ROT_TWO
30 STORE_ATTR 1 (accum)
33 LOAD_CONST 0 (None)
36 RETURN_VALUE
请注意,此优化是在 around 2004(CPython 2.4) 中完成的,以防止用户
a += b 或 a = a + b 的缓慢性,因此它主要用于简单变量,并且仅在下一条指令是 STORE_FAST(局部变量)、STORE_DEREF(闭包)和 STORE_NAME 时才有效。这不是一个通用的解决方案,the best way to do this in Python is to create a list and join its items using str.join。
CPython 实现细节:如果s 和t 都是字符串,则某些Python 实现(例如CPython)通常可以就地执行
优化s = s + t 或s += t 形式的赋值。什么时候
适用,这种优化使二次运行时间大大减少
可能。这个优化既是版本也是实现
依赖。对于性能敏感的代码,最好使用
str.join() 确保一致的线性连接的方法
跨版本和实现的性能。