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字节。
  • 现在来解释上述例子的内存占用情况。
    • 当数值为 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类型是不可变类型。现在我们用图片的方式更清晰的表达。
    • 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.1 时,Python只能近似的表达:
      # 在以 2 为基数的情况下, 1/10 是一个无限循环小数
      # 1/10被近似为 3602879701896397 / 2 ** 55 ,换成小数形式为:
      0.1000000000000000055511151231257827021181583404541015625
      
  • 正是由于二进制表示浮点数的这种不精确性,导致了上述的错误(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 即可。

三、复数

不常用,就不介绍了。。。

四、数值的运算符及其他操作

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 会引发语法错误。

posted on 2020-06-17 00:14  wangxx06  阅读(520)  评论(0编辑  收藏  举报

导航