Python基础 | 3.1 基本数据类型——数字
一、整数部分引用自:
https://github.com/python/cpython/blob/master/Include/longintrepr.h
https://github.com/python/cpython/blob/master/Objects/longobject.c
https://github.com/python/cpython/blob/master/Include/internal/pycore_interp.h
二、浮点数部分引用自:
https://github.com/python/cpython/blob/master/Include/floatobject.h
https://github.com/python/cpython/blob/master/Objects/floatobject.c
一、整数
1.关于整数的基础
整型(Int) —— 通常被称为是整型或整数,是正或负整数,不带小数点。
- 在Python中,整数理论上是没有长度限制的,具有无限的精度,不存在溢出问题。
- 数字是由数字字面值或内置函数与运算符的结果来创建的。 不带修饰的整数字面值(包括十六进制、八进制和二进制数)会生成整数。
- 在Python 3.6 版本以后,支持使用 下划线 将大数字分割,提高了可读性
>>> salary_year = 1_780_030 >>> print('年薪为:',salary_year,'元') 年薪为: 1780030 元 >>>
- 关于其他进制
- 二进制:0bXX 如:十进制的 13 ,用二进制表示为 0b1101
- 八进制:0oXX 如:十进制的 13 ,用八进制表示为 0o15
- 十六进制:0xXX 如:十进制的 13 ,用十六进制表示为 0xD
2.关于整数的深入理解
- int的对象结构 —— PyLongObject:
[longintrepr.h] struct _longobject { PyObject_VAR_HEAD digit ob_digit[1]; };
- int的类型对象 —— PyLong_Type:
[longobject.c] PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "int", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ long_to_decimal_string, /* tp_repr */ &long_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)long_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */ long_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ long_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ long_methods, /* tp_methods */ 0, /* tp_members */ long_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ long_new, /* tp_new */ PyObject_Del, /* tp_free */ };
(1)整数int占用内存大小
问题:Python的整数全是int型,那么它的内存占用是什么情况呢?
- 首先从例子查看内存使用情况。
import sys number_0 = 0 number_1 = 1 number_999 = 999 number_big = 2 ** 30 print('the size of 0 is:',sys.getsizeof(number_0),'bytes') print('the size of 1 is:',sys.getsizeof(number_1),'bytes') print('the size of 999 is:',sys.getsizeof(number_999),'bytes') print('the size of big number is:',sys.getsizeof(number_big),'bytes') # 输出结果为: the size of 0 is: 24 bytes the size of 1 is: 28 bytes the size of 999 is: 28 bytes the size of big number is: 32 bytes
- 从这个例子看到,数字0的占用内存为24字节;数字1占用内存28字节;数字999占用28字节;数字230占用内存32字节。
- 通过源码对内存占用的分析:
- PyLongObject扁平化之后,得到(电脑系统64位的情况下):
struct PyLongObject { Py_ssize_t ob_refcnt; // 8 bytes PyTypeObject *ob_type; // 8 bytes Py_ssize_t ob_size; // 8 bytes unsigned int ob_digit[1]; // 4 bytes * abs(ob_size) };
- 从上述结构可以看到,创建整数对象时,头部固定24字节。分别为:
- ob_refcnt 用作引用计数的计数器,占用8字节;
- *ob_type 指向 PyTypeObject 的指针,用来指定一个对象类型的类型对象,占8字节;
- ob_size 引入ob_size,数据部分设计为变长的数组,ob_size保存的是数组的长度,占8字节。
- 数据部分:
- ob_digit[1] 设置为数组,数组内的一个元素,占4字节。
- 因此,数据部分占用的总字节数就是 —— (4字节 x ob_size)。
- 那么数组内,每个数组元素占4个字节,共32位。但是只有30位用于表述值。因此,数据每增大 230 时,内存大小增加 4字节。
- PyLongObject扁平化之后,得到(电脑系统64位的情况下):
- 现在来解释上述例子的内存占用情况。
- 当数值为 0 时,因为0比较独特,所以我们认为没有数据部分(自己猜测,错误请指出),ob_szie = 0,因此只占用了头部24字节;
- 当数值为 1 时,数值小于 232,因此,ob_size = 1,此时占用28字节;
- 当数值为 999 时,数值仍然小于 230,因此,ob_size = 1,此时占用28字节;
- 当数值为 230 时,数值不小于 230,因此,ob_size = 2,此时占用32字节;
(2)整数int理论上无限精度
问题:为什么int是无限精度的呢?
- PyLongObject扁平化之后,得到(电脑系统64位的情况下):
struct PyLongObject { long ob_refcnt; // 8 bytes struct _typeobject *ob_type; // 8 bytes long ob_size; // 8 bytes unsigned int ob_digit[1]; // 4 bytes * abs(ob_size) };
- 从上述源码可以看出,数组长度可以非常大,并且数组长度每增加 1 ,数值表示范围扩大 230 倍。
- 以下是我的猜测,错误期待您的指出:
- ob_size每增加1,该整数占用内存增加4字节。
- ob_size共8字节,32位。以30位算,可保存的最大十进制数值为:230 = 1073741824
- 那么此时,这个int对象的数值部分就占用了 1073741824 * 4字节
- 1GB=1073741824字节,因此,仅仅这个数值占用了 4G 的内存空间。。。
- 以下是我的猜测,错误期待您的指出:
(3)int类型是定长对象
问题:int类型是属于定长对象还是变长对象
- 前面我们已经看到,int类型申请内存时,对于数值在某一范围内的不同数字,申请的内存大小完全相同,因此,int类型是定长对象。
(4)int类型是不可变类型
问题:int类型是的数值改变之后,内存地址会更改吗?
- 可变数据类型 与 不可变数据类型
- 可变数据类型:当对象的值改变之后,内存地址id()不改变。【即内存上保存的值可以更改】
- 不可变数据类型:当对象的值改变之后,内存地址id()改变。【即内存上保存的值无法更改】
- 首先从例子查看是 可变数据类型 还是 不可变数据类型
number = 99 print(id(number)) number = 100 print(id(number)) # 输出结果为 9219328 9219360
- 从例子中,我们发现int类型是不可变类型。现在我们用图片的方式更清晰的表达。
- 可以看到,当number的值从99变为100时,并不是在原有的内存地址9219328内将99改为100;而是重新开辟了内存空间9219360,存储了数值100。
(5)int使用小整数池
问题1:小整数经常会被用到,以及Python内部也经常使用,那么怎么节省内存并且提升效率呢?
问题2:为什么设置 a = 1,但是1被引用了许多次呢?
-
关于小整数引用次数的例子
import sys a = 1 b = a print(sys.getrefcount(a)) # 输出结果为 133
-
Python为了节约内存并且节约效率,设置了小整数池。范围是[-5,257)。
- 小整数池内的小整数对象经常被全局解释器调用,因此引用次数会很多。例如,上述整数对象1,被a、b以及sys.getrefcount()引用,输出值应该为3,这里输出却为133.
- 小整数池在python解释器运行时,就已经创建。所以,当运行 a = 1 时,实际上并没有创建对象1,而是直接引用已经存在的对象1.
- 小整数池内的小整数不会被Python的垃圾回收机制回收。
-
小整数池相关源码
[longobject.c] // line 22~23 #define NSMALLPOSINTS _PY_NSMALLPOSINTS #define NSMALLNEGINTS _PY_NSMALLNEGINTS // line 37~39 #if NSMALLNEGINTS + NSMALLPOSINTS > 0 #define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS) #define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS) [pycore_interp.h] // line 140~143 /* interpreter state */ #define _PY_NSMALLPOSINTS 257 #define _PY_NSMALLNEGINTS 5
- 根据上述代码,可以明确看到小整数池的范围是 [-5,257)。
- 小整数池的范围可以在 文件pycore_interp.h 中自行修改,但是需要重新编译(CPython基于C语言)。
(6)int的大整数
Python中,大整数的引用数为0时,就会被GC回收(不同于小整数池)。
- 在Python的实践中,发现两个相等的大整数的内存地址,在交互式编程和脚本式编程是不相同的
- 交互式编程(注意赋值方式的区别)
>>> a = 1000 >>> b = 1000 >>> id(a) 140062932662384 >>> id(b) 140062931557008 >>> c = 1001 >>> d = c >>> id(c) 140062931556944 >>> id(d) 140062931556944 >>>
- 脚本式编程(注意赋值方式的区别)
# 代码: a = 1000 b = 1000 print(id(a),id(b)) c =1001 d = c print(id(c),id(d)) # 输出结果: 140546750388336 140546750388336 140546750388176 140546750388176
- 交互式编程中,a 和 b 的地址不相同;脚本式编程中,a和 b 的地址相同。
- 交互式编程(注意赋值方式的区别)
二、浮点数
1.关于浮点数的基础
浮点型(float) - 浮点型由整数部分与小数部分组成,浮点型也可以使用科学计数法表示
- Python中,浮点数float提供17位数字的精度
>>> a = 1.1234567890123456789 >>> a 1.1234567890123457 # 共17位 >>> >>> b = 123.1234567890123456789 >>> b 123.12345678901235 # 共17位 >>>
- Python中,浮点数float还可以表示范围从1.7e-308 ~ 1.7e+308 的指数
- 1.7e+28 = 1.7 * 1028 ; 1.7e-28 = 1.7 * 10-28
>>> 1.123456789012345678e+308 1.1234567890123456e+308 >>> 1.8e+308 inf >>>
- Python中,浮点数float占用24字节
>>> import sys >>> >>> a = 1.1234567890123456789 >>> b = 123.1234567890123456789 >>> c = 1.123456789012345678e+308 >>> sys.getsizeof(a) 24 >>> sys.getsizeof(b) 24 >>> sys.getsizeof(c) 24 >>>
- Python中,浮点型float并不是精确的
>>> 0.1 + 0.2 == 0.3 False >>>
2.关于浮点型的深入理解
- float的对象结构 —— PyFloatObject:
[floatobject.h] // line 15~18 typedef struct { PyObject_HEAD double ob_fval; } PyFloatObject;
- float的类型对象 —— PyFloat_Type:
[floatobject.h] // line 1893~1932 PyTypeObject PyFloat_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "float", sizeof(PyFloatObject), 0, (destructor)float_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ (reprfunc)float_repr, /* tp_repr */ &float_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)float_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ float_new__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ float_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ float_methods, /* tp_methods */ 0, /* tp_members */ float_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ float_new, /* tp_new */ };
(1)浮点型flaot内存占用情况
- 根据float对象结构的源码,对其进行扁平化处理得到:
typedef struct { Py_ssize_t ob_refcnt; // 8 bytes PyTypeObject *ob_type; // 8 bytes double ob_fval; // 8 bytes } PyFloatObject;
- float是定长类型,固定大小为 24字节,因此表示的数字有大小范围。提供大约17位的精度和从1.7e-308 ~ 1.7e+308 的指数。
- 其中,引用计数占用8字节;
- 类型信息占用8字节;
- 具体数值占用8字节(数值保存在ob_fval中)。
(2)关于float精度的问题
- 问题:为什么 0.1 + 0.2 == 0.3 的返回结果是 Flase ?
>>> 0.1 + 0.2 == 0.3 False >>>
- 浮点数在计算机硬件中表示为以 2 为基数(二进制)的小数。
- 例如,要表达一个小数 0.125
- 使用十进制的小数: 1 * 1/10 + 2 * 1/100 + 5 * 1/1000
- 使用二进制的小数: 0 * 1/2 + 0 * 1/4 + 1 * 1/8
- 例如,要表达一个小数 0.125
- 但是,通常情况下,十进制小数不能精确的用二进制表达
- 例如:输入小数 0.1 时,Python只能近似的表达:
# 在以 2 为基数的情况下, 1/10 是一个无限循环小数 # 1/10被近似为 3602879701896397 / 2 ** 55 ,换成小数形式为: 0.1000000000000000055511151231257827021181583404541015625
- 例如:输入小数 0.1 时,Python只能近似的表达:
- 正是由于二进制表示浮点数的这种不精确性,导致了上述的错误(0.1 + 0.2 == 0.3 return flase)
(3)float是定长对象
- 可以看到,float的大小固定为24字节,因此是定长对象。
(4)float是不可变数据类型
- float是不可变数据类型(可以看到,值更改之后,内存地址也相应的发生改变)
>>> a = 1.11 >>> id(a) 140458761572928 >>> a = 1.1 >>> id(a) 140458761572688 >>>
(5)float的缓存机制
- 类似于int类型的小整数池,为了应对频繁开辟、回收内存空间,float类型也有相应的缓存机制。
[floatobject.c] // line 22~24 #ifndef PyFloat_MAXFREELIST # define PyFloat_MAXFREELIST 100 #endif
- free_list缓存链表
- 编译器将PyFloatObject对象从refchain链表中拿出来,组成了单向链表free_list。
- 单向链表free_list 最大长度为100.
- 代码例子说明
>>> a = 1.234 >>> id(a) 140458761572928 >>> del a >>> b = 1.2 >>> id(b) 140458761572928 >>>
- 当 a 的引用被删除时,将内存还给 缓存链表。当有新的float定义时,直接从缓存链表中拿到地址,并将值赋给 ob_fval 即可。
- free_list缓存链表
三、复数
不常用,就不介绍了。。。
四、数值的运算符及其他操作
1.int和float的操作
(1)int的作为数字的操作
- static PyNumberMethods long_as_number
[longobject.c] // line 5595~5630 static PyNumberMethods long_as_number = { (binaryfunc)long_add, /*nb_add*/ (binaryfunc)long_sub, /*nb_subtract*/ (binaryfunc)long_mul, /*nb_multiply*/ long_mod, /*nb_remainder*/ long_divmod, /*nb_divmod*/ long_pow, /*nb_power*/ (unaryfunc)long_neg, /*nb_negative*/ long_long, /*tp_positive*/ (unaryfunc)long_abs, /*tp_absolute*/ (inquiry)long_bool, /*tp_bool*/ (unaryfunc)long_invert, /*nb_invert*/ long_lshift, /*nb_lshift*/ long_rshift, /*nb_rshift*/ long_and, /*nb_and*/ long_xor, /*nb_xor*/ long_or, /*nb_or*/ long_long, /*nb_int*/ 0, /*nb_reserved*/ long_float, /*nb_float*/ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ long_div, /* nb_floor_divide */ long_true_divide, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ long_long, /* nb_index */ };
(2)float的作为数字的操作
- static PyNumberMethods float_as_number
[floatobject.c] // line 1857~1891 static PyNumberMethods float_as_number = { float_add, /* nb_add */ float_sub, /* nb_subtract */ float_mul, /* nb_multiply */ float_rem, /* nb_remainder */ float_divmod, /* nb_divmod */ float_pow, /* nb_power */ (unaryfunc)float_neg, /* nb_negative */ float_float, /* nb_positive */ (unaryfunc)float_abs, /* nb_absolute */ (inquiry)float_bool, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ float___trunc___impl, /* nb_int */ 0, /* nb_reserved */ float_float, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ float_floor_div, /* nb_floor_divide */ float_div, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ };
2.数值的运算符
(1)算数运算符
序号 | 运算 | 结果 | 详解 |
---|---|---|---|
1 | x + y | x 与 y 的和 | |
2 | x - y | x 和 y 的差 | |
3 | x * y | x 和 y 的乘积 | |
4 | x / y | x 和 y 的商 | |
5 | x // y | x 和 y 的商数 | 下述a |
6 | x % y | x 和 y 除后的余数 | 下述b |
7 | -x | x 取反 | |
8 | +x | x 不变 | |
9 | abs(x) | x 的绝对值或大小 | |
10 | int(x) | 将 x 转换为整数 | |
11 | float(x) | 将 x 转换为浮点数 | |
12 | pow(x, y) | x 的 y 次幂 | |
13 | x ** y | x 的 y 次幂 | |
14 | divmod(x, y) | (x // y, x % y) | 下述c |
-
a.算术运算 x // y
>>> a = 7 >>> b = 2 >>> a // b 3 >>> c = 7.0 >>> d = 2 >>> c // d 3.0 >>>
- 可以看到,只取商。并且,除数被除数为int时,结果是int;只要有一个是float时,商就是float类型。
-
b.算术运算 x % y
>>> a = 7 >>> b = 2 >>> a % b 1 >>> c = 7.0 >>> d = 2 >>> c % d 1.0 >>>
-
c.算术运算 divmod(x, y)
>>> a = 7 >>> b = 2 >>> divmod(a,b) (3, 1) >>> c = 7.0 >>> d = 2 >>> divmod(c,d) (3.0, 1.0) >>>
(2)赋值运算符
- 在算术运算符后面加上 = 即可。
- 例如: a += b ,含义为: a = a + b
- 例如: a /= b ,含义为: a = a / b
(3)比较运算符
- 返回结果都是 True 或者 Flase。
序号 | 运算 | 结果 |
---|---|---|
1 | < | 严格小于 |
2 | <= | 小于或等于 |
3 | > | 严格大于 |
4 | >= | 大于或等于 |
5 | == | 等于 |
6 | != | 不等于 |
(4)位运算符
- 仅针对二进制
- a = 0011 1100
- b = 0000 1101
序号 | 运算符 | 描述 | 实例 |
---|---|---|---|
1 | & | 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
2 | | | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
3 | ^ | 按位异或运算符:当两对应的二进位相异时,结果为1 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
4 | ~ | 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
5 | << | 左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
6 | >> | 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
(5)布尔(逻辑)运算符
- 布尔类型
- 0 或者 1 ;其中,0 表示 False ,1表示 True
序号 | 运算符 | 逻辑表达式 | 实例 |
---|---|---|---|
1 | and | x and y | 布尔"与" - if x is false, then y, else x |
2 | or | x or y | (布尔"或" - if x is false, then x, else y |
3 | not | not x | 布尔"非" - if x is false, then True, else False |
- and 是个短路运算符,因此只有在第一个参数为假值时才会对第二个参数求值。
- or 是个短路运算符,因此只有在第一个参数为真值时才会对第二个参数求值。
- not 的优先级比非布尔运算符低,因此 not a == b 会被解读为 not (a == b) 而 a == not b 会引发语法错误。