- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
深入研究 Python 的源代码后,我发现它维护了一个 PyInt_Object
数组,范围从 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]
范围内的整数,因此该范围内的整数通常为 but not always一样的。
您看到的 257 是 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 字节码编译器无法执行大规模优化(如静态类型语言),但它做的比你想象的要多。其中之一是分析文字的用法并避免重复它们。
请注意,这与缓存无关,因为它也适用于没有缓存的 float :
>>> 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
(请注意,即使在错误修复版本之间,常量折叠和窥视孔优化器也会改变行为,因此返回 True
或 False
的示例基本上是任意的,并且将来会改变)。
关于为什么您看到创建了两个 PyInt_Object
,我猜想这样做是为了避免文字比较。例如,数字 257
可以用多个字面量表示:
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
解析器有两种选择:
可能 Python 解析器使用了第二种方法,它避免了重写转换代码并且更容易扩展(例如它也适用于 float )。
读取Python/ast.c
文件,解析所有数字的函数是parsenumber
,调用PyOS_strtoul
获取整数值(对于整数)并最终调用 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
用于为函数/lambdas 等插入常量。compiler_add_o
函数将常量存储到一个 dict
对象中,然后紧接着相等的常量将落在同一个槽中,从而在最终字节码中产生一个常量。
关于python - 解释器维护的整数缓存是怎么回事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15171695/
我打算使用 vulkan synchronization examples 之一作为如何处理不经常更新的统一缓冲区的引用。具体来说,我正在看这个: vkBeginCommandBuffer(...);
我对 git 的了解有限。 我已经从 master 创建了一个分支 B1,进行了一些编辑并提交到这个分支。 我想从 B1 创建另一个分支 B2,我在 B2 中进行了一些编辑 而且我还想提交 B2(包含
这是我做的 我创建了一个分支 abc。然后我创建了两个文本文件 one.txt 和 two.txt。然后我将它们提交到分支 abc。然后我从分支中删除文件 one.txt 并将这些更改提交到分支。 现
在我的主分支中,我得到了 2 个文件: file1.txt file2.txt 我从那里创建了名为 b1 的新分支。在b1中,我修改了file2.txt,不小心删除了file1.txt(从磁盘中,当我
我是 git 的新手。 我创建了一个分支,进行了更改,现在我想 merge 回 master 以使它们“永久化”。 所以我执行了 git merge 1.2 报告为已经是最新的,在 master 上执
我在一个新团队中,工作方式与我以前习惯的完全不同,我们在功能分支上工作,测试人员会在该功能分支上进行测试,然后我们会运行一个 jenkins 作业在该功能被测试签署时将该功能 merge 到开发中,根
我目前正在学习动态内存管理是如何工作的,更具体地说是 realloc 以及它是如何在函数中完成的。 在下面的程序中,我只是想尝试使用 malloc 在函数 a() 中分配一些数字,然后将它们传递给另一
在 Java 中如何从另一个线程分派(dispatch)回主 UI 线程?我正在使用带有 Runnable 的执行器在主 UI 线程之外做一些工作,并且我有一个接口(interface),以便可以通过
我在 git 中有一个项目,所有的事情都直接在 master 分支上完成,标签被用来标记代码的发布版本。我知道这并不理想,并且一直在查看 git 流程,例如:http://nvie.com/posts
我们有一个相当大的 GIT 存储库,我想删除从未 merge 回 master 的分支。 反过来也很好 - 一种列出在某个时候已 merge 到 master 中的所有分支的方法。 我希望首先获取一个
在 Swift 和 C 之间传递字符串时,我看到一些我不理解的行为。请考虑以下 Swift 函数: func demo() { print("\n\n\n\n")
我以前从未合作过,现在我发现自己需要与其他一些人分享这个项目,即使我将完成 90% 的开发工作。 我在 github 上有一个私有(private)仓库。我用 推送了我的初始源 git push or
我们的项目使用 Gitlab,我们有两个长期存在的分支:dev 和 master,类似于 Git Flow。我们正在使用“merge 提交”方法,它将在主分支中创建一个 merge 提交。 但是,由于
我对自定义 View 的绑定(bind)属性有疑问。该属性绑定(bind)到核心数据实体的 NSArrayController。 问题是这样的: 在我看来,我画了几个矩形。这些矩形的位置保存在核心数据
这对我来说似乎太棘手,无法正确执行此操作。 我有一个TreeMap ,我正在获取其中的子图: public static reqObj assignObj(reqObj vArg, i
我有以下 XAML: 所以,基本上我希望将其中一
我正在使用 Angular js 1.3.4 版本并使用 ui-select。 我正在将复杂的多级 JSON 对象数组绑定(bind)到此 ui-select,它工作正常。因此用户可以在此选择中选择任
我正在使用 WebAPI 构建 API,并且一直在使用 NLog 在整个堆栈中进行日志记录。我的 API 解决方案有两个主要项目,包括: 实现 Controller 和 webapi 东西的网站层本身
在 Git 中,给定 (1) 一个分支 A 和 (2) 一个在过去某个时间从 A 派生的分支 B,然后 merge 回 A,我如何才能找到现在 A 中起源于 B 的所有提交? 目的是确定现在在 A 中
假设我的 Controller 如下所示: public class myController { private MyCustomItem acte; ... // gett
我是一名优秀的程序员,十分优秀!