python – 解释器维护的整数缓存是什么?
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了python – 解释器维护的整数缓存是什么?,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4341字,纯文字阅读大概需要7分钟。
内容图文
深入研究Python的源代码后,我发现它维护了一个PyInt_Objects数组,范围从int(-5)到int(256)(@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缓存[-5, 256]范围内的整数,因此预期该范围内的整数也是相同的.
你看到的是Python编译器在同一文本的一部分时优化相同的文字.
在Python shell中输入时,每一行都是一个完全不同的语句,在不同的时刻进行解析,因此:
>>> a = 257
>>> b = 257
>>> a is b
False
但是如果将相同的代码放入文件中:
$echo 'a = 257
> b = 257
> print a is b' > testing.py
$python testing.py
True
只要解析器有机会分析文字的使用位置,例如在交互式解释器中定义函数时,就会发生这种情况:
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
注意编译的代码如何包含257的单个常量.
总之,Python字节码编译器无法执行大规模优化(如静态类型语言),但它比您想象的要多.其中一个方面是分析文字的使用并避免重复它们.
请注意,这与缓存无关,因为它也适用于没有缓存的浮点数:
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
对于更复杂的文字,如元组,它“不起作用”:
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
但是元组内的文字是共享的:
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
关于为什么你看到创建了两个PyInt_Object,我猜这是为了避免文字比较.例如,数字257可以用多个文字表示:
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
解析器有两个选择:
>在创建整数之前将文字转换为某个公共基础,并查看文字是否相同.然后创建一个整数对象.
>创建整数对象并查看它们是否相等.如果是,则只保留一个值并将其分配给所有文字,否则,您已经有要分配的整数.
可能Python解析器使用第二种方法,它避免重写转换代码,并且它更容易扩展(例如它也适用于浮点数).
读取Python / ast.c文件,解析所有数字的函数是parsenumber,它调用PyOS_strtoul获取整数值(对于intgers)并最终调用PyLong_FromString:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
正如您在此处所看到的,解析器不检查它是否已找到具有给定值的整数,因此这解释了为什么您看到创建了两个int对象,
这也意味着我的猜测是正确的:解析器首先创建常量,然后仅优化字节码以使用相同的对象获得相等的常量.
执行此检查的代码必须位于Python / compile.c或Python / peephole.c中,因为这些是将AST转换为字节码的文件.
特别是,compiler_add_o函数似乎就是这样做的.在compiler_lambda中有这样的评论:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
所以似乎compiler_add_o用于插入函数/ lambda等的常量.
compiler_add_o函数将常量存储到一个dict对象中,紧接着就是相等的常量将落在同一个槽中,从而在最终的字节码中产生一个常量.
内容总结
以上是互联网集市为您收集整理的python – 解释器维护的整数缓存是什么?全部内容,希望文章能够帮你解决python – 解释器维护的整数缓存是什么?所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。