注:本文不区分作为编程语言的Python和作为语言实现的Python。后者均默认为CPython。

了解他人对Python源代码的掌握情况,我喜欢问这样一个问题

请问,在Python中,256和257的主要区别是什么?

我期望的回答是

Python内部,对这两个数采取了不同的对象创建策略

1.做一个实验

我们知道,在一个对象的生存期内,可以用id()函数得到这个对象的唯一标识。即,id返回值相同的对象一定是同一个对象。

启动Python的交互模式(主流版本的Python 2和Python 3均可),输入以下语句并观察结果。

  1. >>> a = 0
  2. >>> b = 0
  3. >>> id(a) == id(b)
  4. True
  5. >>> a = -5
  6. >>> b = -5
  7. >>> id(a) == id(b)
  8. True
  9. >>> a = -6
  10. >>> b = -6
  11. >>> id(a) == id(b)
  12. False
  13. >>> a = 256
  14. >>> b = 256
  15. >>> id(a) == id(b)
  16. True
  17. >>> a = 257
  18. >>> b = 257
  19. >>> id(a) == id(b)
  20. False
  21. >>> a = -1.0
  22. >>> b = -1.0
  23. >>> id(a) == id(b)
  24. False

你也可以写一个带有for循环的脚本,更加全面的验证这样一个结论:

Python中,对于整数对象,如果其值处于[-5,256]的闭区间内,则值相同的对象是同一个对象

您有可能想到了,这也许和Python内部的某种机制有关。让我们更加深入的使用API来验证这个结论。

2. 使用Python API

以Python 2为例,可以使用这样的代码得到与Python脚本等价的结论:

  1. #include <Python.h>
  2. int main(int argc,char ** argv) {
  3. PyObject *a,*b;
  4. Py_SetProgramName(argv[0]);
  5. Py_Initialize();
  6. a = PyInt_FromLong(256);
  7. b = PyInt_FromLong(256);
  8. printf("a=%p,b=%p\n",a,b); //value should be the same
  9. a = PyInt_FromLong(257);
  10. b = PyInt_FromLong(257);
  11. printf("a=%p,b=%p\n",a,b); //value should be different
  12. Py_Finalize();
  13. return 0;
  14. }

如果使用Python3 ,PyInt_FromLong要替换为PyLong_FromLong。

从运行结果可以看到,从256产生的两个PyObject*,指向了内存中相同的地址,但是从257产生的PyObject则是相互独立的。

3.没有什么好奇怪的

为什么是-5到256之间的这200多个数?其实没有什么奇怪的,Python本身就是这样实现的。

让我们打开源代码一看究竟。首先看看Python2的实现方式。下面的代码是以本文写作时最新的2.7.14为例子的。

在Python自身的main函数里,会调用Py_Initialize这个函数初始化Python内部的一系列模块。(Modules/main.c,551行)。在初始化过程中,_PyInt_Init会被调用(Python/pythonrun.c,210行)。_PyInt_Init的唯一作用就是初始化small_ints数组(Objects/intobject.c,1452行):

  1. int
  2. _PyInt_Init(void)
  3. {
  4. PyIntObject *v;
  5. int ival;
  6. #if NSMALLNEGINTS + NSMALLPOSINTS > 0
  7. for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
  8. if (!free_list && (free_list = fill_free_list()) == NULL)
  9. return 0;
  10. /* PyObject_New is inlined */
  11. v = free_list;
  12. free_list = (PyIntObject *)Py_TYPE(v);
  13. (void)PyObject_INIT(v, &PyInt_Type);
  14. v->ob_ival = ival;
  15. small_ints[ival + NSMALLNEGINTS] = v;
  16. }
  17. #endif
  18. return 1;
  19. }

我们看到了两个宏:NSMALLNEGINTS 和NSMALLPOSINTS 。在intobject.c的头部找到它们的定义:

  1. #ifndef NSMALLPOSINTS
  2. #define NSMALLPOSINTS 257
  3. #endif
  4. #ifndef NSMALLNEGINTS
  5. #define NSMALLNEGINTS 5
  6. #endif

从_PyInt_Init的实现上,我们可以看到被放入small_ints的数字范围是-5到256。因此,你可以通过修改源代码的方式,将这个范围任意的扩展。

如果你不对这两个宏进行改动,那么在Python启动的时候,会先创建一个200多PyObject大小的数组,默认的把-5从256的所有整数创建完毕。

我们知道,Python在遇到诸如 a = 5这样的语句的时候,最终会落到PyInt_FromLong这个函数里。我们看看这个函数是怎么写的(intobject.c,86行):

  1. PyObject *
  2. PyInt_FromLong(long ival)
  3. {
  4. register PyIntObject *v;
  5. #if NSMALLNEGINTS + NSMALLPOSINTS > 0
  6. if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
  7. v = small_ints[ival + NSMALLNEGINTS];
  8. Py_INCREF(v);
  9. #ifdef COUNT_ALLOCS
  10. if (ival >= 0)
  11. quick_int_allocs++;
  12. else
  13. quick_neg_int_allocs++;
  14. #endif
  15. return (PyObject *) v;
  16. }
  17. #endif
  18. if (free_list == NULL) {
  19. if ((free_list = fill_free_list()) == NULL)
  20. return NULL;
  21. }
  22. /* Inline PyObject_New */
  23. v = free_list;
  24. free_list = (PyIntObject *)Py_TYPE(v);
  25. (void)PyObject_INIT(v, &PyInt_Type);
  26. v->ob_ival = ival;
  27. return (PyObject *) v;
  28. }

这段代码很容易读懂,首先判断字面量的范围是不是在-5到256之间,如果是,直接从small_ints里面取得缓存的对象,如果不是,再通过PyObject_New来创建一个新的对象。

Python3 的代码以此类推,相似的代码在longobject.c里面。

4. 为什么要这样做

主要还是性能上的考虑。由于创建一个新的对象是比较折腾的:在内存池中分配空间,赋予对象的类别并赋予其初始的值。从-5到256这些小的整数,在Python脚本中使用的非常频繁,又因为他们是不可更改的,因此只创建一次,重复使用就可以了。

有兴趣的读者可以把负责缓存边界的两个宏改小,或者让它们的和是负数以取消这个功能,看看日常的脚本是否有性能的变化。

5. 一点扩展……

考虑这样的命令:

  1. >>> a,b = 400,400
  2. >>> id(a) == id(b)
  3. True

您知道是为什么吗?


以上内容转载自 知乎

Python中神秘的-5到256的更多相关文章

  1. Python中的xxx == xx是否等价于xxx is xxx

    先上代码: >>> a = 1 >>> b = 1 >>> a is b True >>> a == b True what? ...

  2. python 中的unicode详解

    通过例子来看问题是比较容易懂的. 首先来看,下面这个是我新建的一个txt文件,名字叫做ivan_utf8.txt,然后里面随便编辑了一些东西. 然后来用控制台打开这个文件,同样也是截图: 这里就是简单 ...

  3. Python中is和==的区别

    Python中有很多运算符,今天我们就来讲讲is和==两种运算符在应用上的本质区别是什么. 在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识) ...

  4. 【循序渐进学Python】3. Python中的序列——字符串

    字符串是零个或多个的字符所组成的序列,字符串是Python内建的6种序列之一,在Python中字符串是不可变的. 1. 格式化字符串 字符串格式化使用字符串格式化操作符即百分号%来实现.在%左侧放置一 ...

  5. python中的is、==和cmp()比较字符串

    python 中的is.==和cmp(),比较字符串 经常写 shell 脚本知道,字符串判断可以用 =,!= 数字的判断是 -eq,-ne 等,但是 Python 确不是这样子地.所以作为慢慢要转换 ...

  6. 8.python中的数字

    python中数字对象的创建如下, a = 123 b = 1.23 c = 1+1j 可以直接输入数字,然后赋值给变量. 同样也可是使用类的方式: a = int(123) b = float(1. ...

  7. Python中的字符串处理

    Python转义字符 在需要在字符中使用特殊字符时,python用反斜杠(\)转义字符.如下表: 转义字符 描述 \(在行尾时) 续行符 \\ 反斜杠符号 \' 单引号 \" 双引号 \a ...

  8. python中的subprocess.Popen()使用

    python中的subprocess.Popen()使用 从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回 ...

  9. Python中星号的本质和使用方式

    翻译:Python 开发者 - 一汀, 英文:Trey Hunner http://blog.jobbole.com/114655/ Python开发者 在 Python 中有很多地方可以看到*和** ...

随机推荐

  1. 7-8 Left-pad

    思路 注意读入和输出格式 如果用fgets读入的话会带上回车,输出的时候一定不要输出了双回车 并且此时的length也会比原始长度多了一,要注意长度比较,这里容易出错 代码 #include < ...

  2. 【想见你】剧情解析byZlc

    花两天时间刷完了想见你,精神有点恍惚. 要是刷题也能有这个尽头就好了... 下面给大家带来个人的剧(hu)情(bian)解(luan)析(zao) 穿越条件:一台老式随身听,一首last dance, ...

  3. python+matplotlib制作雷达图3例分析和pandas读取csv操作

    1.例一 图1 代码1 #第1步:导出模块 import numpy as np import matplotlib.pyplot as plt from matplotlib import font ...

  4. Python socket day5

    下载文件 程序04,05 服务端在接收到文件名时应该用try来打开文件,不应该用with open来打开否则,如果文件名不存在,用with open 会出错误 客户端要判断服务端发送的数据是否为空,不 ...

  5. 【C语言】用函数实现两个数排序(指针作函数参数)

    原理就不讲了,这里用来理解指针的使用方法 #include <stdio.h> void fun(int* a,int* b) { int t; if(*a>=*b) { t = * ...

  6. 精简版logging

    # coding=utf-8 import logging import time import os import logging.handlers import re def logger(sch ...

  7. JAVA基础学习(3)之循环

    3循环 3.1循环 3.1.1循环 一直要做的行为进行循环 3.1.2数数字 while(){}判断是否进行 数数字:number/10 //数数字Scanner in = new Scanner(S ...

  8. Spring Boot 编辑器 IDEA 免费许可申请

    最近 IDEA 陆续到期(试用版)听说可以申请开源许可,试试吧. 点击 https://www.jetbrains.com/shop/eform/opensource?product=ALL 填写相关 ...

  9. vue 路由传参 以及获取参数

    1.通过query实现: <router-link :to="{ name:'home',query:{id:1} }">跳转页面</router-link> ...

  10. 吴裕雄 python 神经网络——TensorFlow实现AlexNet模型处理手写数字识别MNIST数据集

    import tensorflow as tf # 输入数据 from tensorflow.examples.tutorials.mnist import input_data mnist = in ...