python 函数中的硬编码变量
Hard coded variables in python function
有时,一些 values/strings 被硬编码在函数中。例如,在下面的函数中,我定义了一个 "constant" 比较字符串并对其进行检查。
def foo(s):
c_string = "hello"
if s == c_string:
return True
return False
没有过多讨论为什么这样做不好,以及应该如何在外部范围内定义它,我想知道当它是时幕后发生了什么这样定义的。
每次调用都会创建字符串吗?
如果不是字符串 "hello"
而是列表:[1,2,3]
(或者如果重要的话,包含可变内容的列表)会发生同样的情况吗?
因为字符串是不可变的(就像元组一样),它与函数的字节码对象一起存储。它通过非常简单和快速的索引查找加载。这实际上比全局查找快。
您可以在字节码的反汇编中看到这一点,使用 dis.dis()
function:
>>> import dis
>>> def foo(s):
... c_string = "hello"
... if s == c_string:
... return True
... return False
...
>>> dis.dis(foo)
2 0 LOAD_CONST 1 ('hello')
3 STORE_FAST 1 (c_string)
3 6 LOAD_FAST 0 (s)
9 LOAD_FAST 1 (c_string)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 22
4 18 LOAD_GLOBAL 0 (True)
21 RETURN_VALUE
5 >> 22 LOAD_GLOBAL 1 (False)
25 RETURN_VALUE
>>> foo.__code__.co_consts
(None, 'hello')
LOAD_CONST
操作码从 co_costs
数组加载字符串对象,该数组是函数代码对象的一部分;引用被推到堆栈的顶部。 STORE_FAST
操作码从堆栈顶部获取引用并将其存储在本地数组中,这也是一个非常简单快速的操作。
对于可变文字({..}
、[..]
)特殊的操作码构建对象,内容仍然尽可能地被视为常量(更复杂的结构只是遵循相同的构建块):
>>> def bar(): return ['spam', 'eggs']
...
>>> dis.dis(bar)
1 0 LOAD_CONST 1 ('spam')
3 LOAD_CONST 2 ('eggs')
6 BUILD_LIST 2
9 RETURN_VALUE
BUILD_LIST
调用使用两个常量字符串对象创建新的列表对象。
有趣的事实:如果您使用列表对象进行成员资格测试(something in ['option1', 'option2', 'option3']
Python 知道列表对象永远不会发生变化,并且会在编译时将其转换为元组(所谓的窥孔优化。这同样适用于集合文字,它被转换为 frozenset()
对象,但仅限于 Python 3.2 和更新版本。参见 Tuple or list when using 'in' in an 'if' clause?
请注意,您的示例函数使用的布尔值相当冗长;你本可以使用:
def foo(s):
c_string = "hello"
return s == c_string
对于完全相同的结果,避免在 Python 2 中调用 LOAD_GLOBAL
(Python 3 制作了 True
和 False
关键字,因此值可以也存储为常量)。
有时,一些 values/strings 被硬编码在函数中。例如,在下面的函数中,我定义了一个 "constant" 比较字符串并对其进行检查。
def foo(s):
c_string = "hello"
if s == c_string:
return True
return False
没有过多讨论为什么这样做不好,以及应该如何在外部范围内定义它,我想知道当它是时幕后发生了什么这样定义的。
每次调用都会创建字符串吗?
如果不是字符串 "hello"
而是列表:[1,2,3]
(或者如果重要的话,包含可变内容的列表)会发生同样的情况吗?
因为字符串是不可变的(就像元组一样),它与函数的字节码对象一起存储。它通过非常简单和快速的索引查找加载。这实际上比全局查找快。
您可以在字节码的反汇编中看到这一点,使用 dis.dis()
function:
>>> import dis
>>> def foo(s):
... c_string = "hello"
... if s == c_string:
... return True
... return False
...
>>> dis.dis(foo)
2 0 LOAD_CONST 1 ('hello')
3 STORE_FAST 1 (c_string)
3 6 LOAD_FAST 0 (s)
9 LOAD_FAST 1 (c_string)
12 COMPARE_OP 2 (==)
15 POP_JUMP_IF_FALSE 22
4 18 LOAD_GLOBAL 0 (True)
21 RETURN_VALUE
5 >> 22 LOAD_GLOBAL 1 (False)
25 RETURN_VALUE
>>> foo.__code__.co_consts
(None, 'hello')
LOAD_CONST
操作码从 co_costs
数组加载字符串对象,该数组是函数代码对象的一部分;引用被推到堆栈的顶部。 STORE_FAST
操作码从堆栈顶部获取引用并将其存储在本地数组中,这也是一个非常简单快速的操作。
对于可变文字({..}
、[..]
)特殊的操作码构建对象,内容仍然尽可能地被视为常量(更复杂的结构只是遵循相同的构建块):
>>> def bar(): return ['spam', 'eggs']
...
>>> dis.dis(bar)
1 0 LOAD_CONST 1 ('spam')
3 LOAD_CONST 2 ('eggs')
6 BUILD_LIST 2
9 RETURN_VALUE
BUILD_LIST
调用使用两个常量字符串对象创建新的列表对象。
有趣的事实:如果您使用列表对象进行成员资格测试(something in ['option1', 'option2', 'option3']
Python 知道列表对象永远不会发生变化,并且会在编译时将其转换为元组(所谓的窥孔优化。这同样适用于集合文字,它被转换为 frozenset()
对象,但仅限于 Python 3.2 和更新版本。参见 Tuple or list when using 'in' in an 'if' clause?
请注意,您的示例函数使用的布尔值相当冗长;你本可以使用:
def foo(s):
c_string = "hello"
return s == c_string
对于完全相同的结果,避免在 Python 2 中调用 LOAD_GLOBAL
(Python 3 制作了 True
和 False
关键字,因此值可以也存储为常量)。