zoukankan      html  css  js  c++  java
  • python基本数据类型底层实现

    一、前言

    我们知道,python是一种动态语言,可以将任何类型的数据赋给任何变量,譬如:

    # Python代码
    x = 4
    x = "four"

    这里已经将 x 变量的内容由整型转变成了字符串,而同样的操作在 C 语言中将会导致(取决于编译器设置)编译错误或其他未知的后果。

    这种灵活性是使 Python 和其他动态类型的语言更易用的原因之一。理解这一特性如何工作是学习用 Python 有效且高效地分析数据的重要因素。但是这种类型灵活性也指出了一个事实:

    Python 变量不仅是它们的值,还包括了关于值的类型的一些额外信息。

    二、整型

    标准的 Python 实现是用 C 语言编写的。这意味着每一个 Python 对象都是一个伪 C 语言结构体,该结构体不仅包含其值,还有其他信息。例如,当我们在 Python 中定义一个整型,例如 x = 10000 时,x 并不是一个“原生”整型,而是一个指针,指向一个 C 语言的复合结构体,结构体里包含了一些值。查看 Python 3.4 的源代码,可以发现整型(长整型)的定义,如下所示(C 语言的宏经过扩展之后):

    struct _longobject {
        long ob_refcnt;
        PyTypeObject *ob_type;
        size_t ob_size;
        long ob_digit[1];
    };

    Python 3.4 中的一个整型实际上包括 4 个部分。

    • ob_refcnt 是一个引用计数,它帮助 Python 默默地处理内存的分配和回收。
    • ob_type 将变量的类型编码。
    • ob_size 指定接下来的数据成员的大小。
    • ob_digit 包含我们希望 Python 变量表示的实际整型值。

    这意味着与 C 语言这样的编译语言中的整型相比,在 Python 中存储一个整型会有一些开销,正如下图所示:

    image

    这里 PyObject_HEAD 是结构体中包含引用计数、类型编码和其他之前提到的内容的部分。

    两者的差异在于,C 语言整型本质上是对应某个内存位置的标签,里面存储的字节会编码成整型。而 Python 的整型其实是一个指针,指向包含这个 Python 对象所有信息的某个内存位置,其中包括可以转换成整型的字节。由于 Python 的整型结构体里面还包含了大量额外的信息,所以 Python 可以自由、动态地编码。但是,Python 类型中的这些额外信息也会成为负担,在多个对象组合的结构体中尤其明显。

    三、列表

    设想如果使用一个包含很多 Python 对象的 Python 数据结构,会发生什么? Python 中的标准可变多元素容器是列表。可以用如下方式创建一个整型值列表:

    In[1]: L = list(range(10))
           L
    Out[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    In[2]: type(L[0])
    Out[2]: int

    或者创建一个字符串列表:

    In[3]: L2 = [str(c) for c in L]
           L2
    Out[3]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    In[4]: type(L2[0])
    Out[4]: str

    因为 Python 的动态类型特性,甚至可以创建一个异构的列表:

    In[5]: L3 = [True, "2", 3.0, 4]
           [type(item) for item in L3]
    Out[5]: [bool, str, float, int]

    但是想拥有这种灵活性也是要付出一定代价的:为了获得这些灵活的类型,列表中的每一项必须包含各自的类型信息、引用计数和其他信息;也就是说,每一项都是一个完整的 Python 对象。来看一个特殊的例子,如果列表中的所有变量都是同一类型的,那么很多信息都会显得多余——将数据存储在固定类型的数组中应该会更高效。动态类型的列表和固定类型的(NumPy 式)数组间的区别如下图所示。

    image

    在实现层面,数组基本上包含一个指向连续数据块的指针。另一方面,Python 列表包含一个指向指针块的指针,这其中的每一个指针对应一个完整的 Python 对象(如前面看到的 Python 整型)。另外,列表的优势是灵活,因为每个列表元素是一个包含数据和类型信息的完整结构体,而且列表可以用任意类型的数据填充。固定类型的 NumPy 式数组缺乏这种灵活性,但是能更有效地存储和操作数据。

    四、固定类型数组

    1. 使用array模块创建数组

    Python 提供了几种将数据存储在有效的、固定类型的数据缓存中的选项。内置的数组(array)模块(在 Python 3.3 之后可用)可以用于创建统一类型的密集数组:

    In[6]: import array
           L = list(range(10))
           A = array.array('i', L)
           A
    Out[6]: array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

    这里的 'i' 是一个数据类型码,表示数据为整型。

    更实用的是 NumPy 包中的 ndarray 对象。Python 的数组对象提供了数组型数据的有效存储,而 NumPy 为该数据加上了高效的操作。稍后将介绍这些操作,这里先展示几种创建 NumPy 数组的方法。

    从用 np 别名导入 NumPy 的标准做法开始:

    In[7]: import numpy as np

    2. 从python列表创建NumPy 数组

    首先,可以用 np.array 从 Python 列表创建数组:

    In[8]: # 整型数组:
           np.array([1, 4, 2, 5, 3])
    Out[8]: array([1, 4, 2, 5, 3])

    请记住,不同于 Python 列表,NumPy 要求数组必须包含同一类型的数据。如果类型不匹配,NumPy 将会向上转换(如果可行)。这里整型被转换为浮点型:

    In[9]: np.array([3.14, 4, 2, 3])
    Out[9]: array([ 3.14,  4.  ,  2.  ,  3. ])

    如果希望明确设置数组的数据类型,可以用 dtype 关键字:

    In[10]: np.array([1, 2, 3, 4], dtype='float32')
    Out[10]: array([ 1.,  2.,  3.,  4.], dtype=float32)

    最后,不同于 Python 列表,NumPy 数组可以被指定为多维的。以下是用列表的列表初始化多维数组的一种方法:

    In[11]: # 嵌套列表构成的多维数组
            np.array([range(i, i + 3) for i in [2, 4, 6]])
    Out[11]: array([[2, 3, 4],
                    [4, 5, 6],
                    [6, 7, 8]])

    内层的列表被当作二维数组的行。

    3. 从头创建NumPy 数组

    面对大型数组的时候,用 NumPy 内置的方法从头创建数组是一种更高效的方法。以下是几个示例:

    In[12]: # 创建一个长度为10的数组,数组的值都是0
            np.zeros(10, dtype=int)
    
    Out[12]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    
    In[13]: # 创建一个3×5的浮点型数组,数组的值都是1
            np.ones((3, 5), dtype=float)
    
    Out[13]: array([[ 1.,  1.,  1.,  1.,  1.],
                    [ 1.,  1.,  1.,  1.,  1.],
                    [ 1.,  1.,  1.,  1.,  1.]])
    
    In[14]: # 创建一个3×5的浮点型数组,数组的值都是3.14
            np.full((3, 5), 3.14)
    
    Out[14]: array([[ 3.14,  3.14,  3.14,  3.14,  3.14],
                    [ 3.14,  3.14,  3.14,  3.14,  3.14],
                    [ 3.14,  3.14,  3.14,  3.14,  3.14]])
    
    In[15]: # 创建一个3×5的浮点型数组,数组的值是一个线性序列
            # 从0开始,到20结束,步长为2
            # (它和内置的range()函数类似)
            np.arange(0, 20, 2)
    
    Out[15]: array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
    
    In[16]: # 创建一个5个元素的数组,这5个数均匀地分配到0~1
            np.linspace(0, 1, 5)
    
    Out[16]: array([ 0.  ,  0.25,  0.5 ,  0.75,  1. ])
    
    In[17]: # 创建一个3×3的、在0~1均匀分布的随机数组成的数组
            np.random.random((3, 3))
    
    Out[17]: array([[ 0.99844933,  0.52183819,  0.22421193],
                    [ 0.08007488,  0.45429293,  0.20941444],
                    [ 0.14360941,  0.96910973,  0.946117 ]])
    
    
    In[18]: # 创建一个3×3的、均值为0、方差为1的
            # 正态分布的随机数数组
            np.random.normal(0, 1, (3, 3))
    
    Out[18]: array([[ 1.51772646,  0.39614948, -0.10634696],
                    [ 0.25671348,  0.00732722,  0.37783601],
                    [ 0.68446945,  0.15926039, -0.70744073]])
    
    In[19]: # 创建一个3×3的、[0, 10)区间的随机整型数组
            np.random.randint(0, 10, (3, 3))
    
    Out[19]: array([[2, 3, 4],
                    [5, 7, 8],
                    [0, 5, 0]])
    
    In[20]: # 创建一个3×3的单位矩阵
            np.eye(3)
    
    Out[20]: array([[ 1.,  0.,  0.],
                    [ 0.,  1.,  0.],
                    [ 0.,  0.,  1.]])
    
    In[21]: # 创建一个由3个整型数组成的未初始化的数组
            # 数组的值是内存空间中的任意值
            np.empty(3)
    
    Out[21]: array([ 1.,  1.,  1.])

    五、NumPy标准数据类型

    NumPy 数组包含同一类型的值,因此详细了解这些数据类型及其限制是非常重要的。因为 NumPy 是在 C 语言的基础上开发的,所以 C、Fortran 和其他类似语言的用户会比较熟悉这些数据类型。

    数据类型

    描述

    bool_

    布尔值(真、True 或假、False),用一个字节存储

    int_

    默认整型(类似于 C 语言中的 long,通常情况下是 int64 或 int32)

    intc

    同 C 语言的 int 相同(通常是 int32 或 int64)

    intp

    用作索引的整型(和 C 语言的 ssize_t 相同,通常情况下是 int32 或 int64)

    int8

    字节(byte,范围从–128 到 127)

    int16

    整型(范围从–32768 到 32767)

    int32

    整型(范围从–2147483648 到 2147483647)

    int64

    整型(范围从–9223372036854775808 到 9223372036854775807)

    uint8

    无符号整型(范围从 0 到 255)

    uint16

    无符号整型(范围从 0 到 65535)

    uint32

    无符号整型(范围从 0 到 4294967295)

    uint64

    无符号整型(范围从 0 到 18446744073709551615)

    float_

    float64 的简化形式

    float16

    半精度浮点型:符号比特位,5 比特位指数(exponent),10 比特位尾数(mantissa)

    float32

    单精度浮点型:符号比特位,8 比特位指数,23 比特位尾数

    float64

    双精度浮点型:符号比特位,11 比特位指数,52 比特位尾数

    complex_

    complex128 的简化形式

    complex64

    复数,由两个 32 位浮点数表示

    complex128

    复数,由两个 64 位浮点数表示

    上表列出了标准 NumPy 数据类型。请注意,当构建一个数组时,你可以用一个字符串参数来指定数据类型:

    np.zeros(10, dtype='int16')

    或者用相关的 NumPy 对象来指定:

    np.zeros(10, dtype=np.int16)

    还可以进行更高级的数据类型指定,例如指定高位字节数或低位字节数;更多的信息可以在 NumPy 文档(http://numpy.org/)中查看。

    六、参考

    1. 《Python数据科学手册》 [美] Jake VanderPlas [VanderPlas, Jake]

    (完)

  • 相关阅读:
    Android开发总结
    LeakCanary原理分析
    机器学习
    Kivy 中文教程 实例入门 简易画板 (Simple Paint App):2. 实现绘图功能
    Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 3. 循环
    Kivy 中文教程 实例入门 简易画板 (Simple Paint App):1. 自定义窗口部件 (widget)
    Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 2. 变量
    Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 1. 神秘朋友
    Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 0. 准备工作
    远程显示(操作) 服务器 GUI 程序(图形化界面) (基于 X11 Forwarding + Centos + MobaXterm)
  • 原文地址:https://www.cnblogs.com/harrymore/p/10071632.html
Copyright © 2011-2022 走看看