在深入研究了Python的源代码之后,我发现它维护了一个从int(-5)
到int(256)
不等的PyInt_Object
数组(@src/Objects/intobject.c)
一个小小的实验证明了这一点:
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
但如果我在py文件中一起运行这些代码(或者用分号将它们连接起来),结果就不同了:
>>> a = 257; b = 257; a is b
True
我很好奇为什么它们仍然是同一个对象,所以我深入到语法树和编译器中,找到了下面列出的调用层次结构:
PyRun_FileExFlags()
mod = PyParser_ASTFromFile()
node *n = PyParser_ParseFileFlagsEx() //source to cst
parsetoke()
ps = PyParser_New()
for (;;)
PyTokenizer_Get()
PyParser_AddToken(ps, ...)
mod = PyAST_FromNode(n, ...) //cst to ast
run_mod(mod, ...)
co = PyAST_Compile(mod, ...) //ast to CFG
PyFuture_FromAST()
PySymtable_Build()
co = compiler_mod()
PyEval_EvalCode(co, ...)
PyEval_EvalCodeEx()
然后我在PyInt_FromLong
和PyAST_FromNode
之前/之后添加了一些调试代码,并执行了test.py:
a = 257
b = 257
print "id(a) = %d, id(b) = %d" % (id(a), id(b))
输出如下:
DEBUG: before PyAST_FromNode
name = a
ival = 257, id = 176046536
name = b
ival = 257, id = 176046752
name = a
name = b
DEBUG: after PyAST_FromNode
run_mod
PyAST_Compile ok
id(a) = 176046536, id(b) = 176046536
Eval ok
这意味着在cst
到ast
转换期间,创建了两个不同的PyInt_Object
(实际上它是在ast_for_atom()
函数中执行的),但它们后来被合并。
我发现很难理解PyAST_Compile
和PyEval_EvalCode
中的来源,所以我在这里寻求帮助,如果有人给我提示,我会很感激的?
Python缓存范围^{} 中的整数,因此预期该范围中的整数也相同。
您看到的是Python编译器在对相同文本的一部分进行优化时对相同的文本进行优化。
在Python shell中输入时,每一行都是一个完全不同的语句,在不同的时刻解析,因此:
但是如果你把相同的代码放到一个文件中:
每当解析器有机会分析文本的使用位置时,就会发生这种情况,例如在交互式解释器中定义函数时:
请注意编译后的代码如何包含
257
的单个常量。总之,Python字节码编译器无法执行大规模优化(如静态类型语言),但它所做的比您想象的还要多。其中之一就是分析文字的用法,避免重复。
请注意,这与缓存无关,因为它也适用于没有缓存的浮动:
对于更复杂的文本,如元组,它“不起作用”:
但是元组中的文本是共享的:
关于为什么会创建两个
PyInt_Object
,我会猜测这样做是为了避免文字比较。例如,数字257
可以用多个文字表示:解析器有两种选择:
Python解析器可能使用了第二种方法,这种方法避免了重写转换代码,而且更易于扩展(例如,它也可以使用float)。
读取
Python/ast.c
文件时,解析所有数字的函数是parsenumber
,它调用PyOS_strtoul
获取整数值(对于整数),并最终调用PyLong_FromString
:正如您在这里看到的,解析器会检查是否已经找到一个具有给定值的整数,所以这就解释了为什么会创建两个int对象, 这也意味着我的猜测是正确的:解析器首先创建常量,然后才优化字节码,以便对相同的常量使用相同的对象。
执行此检查的代码必须位于
Python/compile.c
或Python/peephole.c
中的某个位置,因为这些文件将AST转换为字节码。尤其是
compiler_add_o
函数似乎就是这样做的。在compiler_lambda
中有此注释:因此,似乎
compiler_add_o
用于为函数/lambdas等插入常量。 函数compiler_add_o
将常量存储到一个dict
对象中,紧接着,相等的常量将落在同一个槽中,从而在最后的字节码中产生一个常量。相关问题 更多 >
编程相关推荐