## 9.3 理解 Python 中的数据类型

> 原文：[Understanding Data Types in Python](https://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/numpy/02.01-Understanding-Data-Types.ipynb)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 
> 本节是[《Python 数据科学手册》](https://github.com/jakevdp/PythonDataScienceHandbook)（Python Data Science Handbook）的摘录。

数据驱动的科学和有效计算需要了解数据的存储和操作方式。本节概述了如何在 Python 语言本身中处理数据数组，以及对比 NumPy 如何改进它。对于理解本书其余部分的大部分内容，理解这种差异至关重要。

Python 的用户通常被它的易用性吸引，其中一部分是动态类型。虽然像 C 或 Java 这样的静态类型语言要求显式声明每个变量，但像 Python 这样的动态类型语言会跳过此规范。 例如，在 C 中，你可以指定特定操作，如下所示：

```C
/* C 代码 */
int result = 0;
for(int i=0; i<100; i++){
    result += i;
}
```

在 Python 中，可以用这种方式编写等效的操作：

```py
# Python 代码
result = 0
for i in range(100):
    result += i
```

注意主要区别：在 C 中，每个变量的数据类型是显式声明的，而在 Python 中，类型是动态推断的。 这意味着，例如，我们可以将任何类型的数据分配给任何变量：

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

这里我们将``x``的内容从整数转换为字符串。 C 中的相同内容会导致编译错误或其他无意义的结果（取决于编译器设置）：

```C
/* C 代码 */
int x = 4;
x = "four";  // 失败
```

这种灵活性，是使 Python 和其他动态类型语言方便易用的一个方面。理解它的原理，是学习如何有效使用 Python 分析数据的一个重要方面。

但是这种类型的灵活性也指出了，Python 变量不仅仅是它们的值; 它们还包含值的类型的额外信息。 我们将在后面的章节中详细探讨它。

### Python 的整数不仅仅是整数

标准的 Python 实现是用 C 编写的。这意味着每个 Python 对象都只是一个巧妙伪装的 C 结构，它不仅包含其值，还包含其他信息。

例如，当我们在 Python 中定义一个整数时，例如``x = 10000``，``x``不仅仅是一个“原始”整数。 它实际上是指向复合 C 结构的指针，包含多个值。

通过 Python 3.4 源代码，我们发现（长）整数类型定义实际上看起来像这样（C 宏扩展之后）：

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

Python 3.4 中的单个整数实际上包含四个部分：

- ``ob_refcnt``, 引用计数，帮助 Python 静默处理内存分配和释放
- ``ob_type``, 它编码变量的类型
- ``ob_size``, 它指定以下数据成员的大小
- ``ob_digit``, 其中包含我们期望 Python 变量表示的实际整数值。

这意味着在 Python 中存储整数，与在 C 等编译语言中的整数相比，存在一些开销，如下图所示：

![Integer Memory Layout](../img/9-3-1.png)

这里``PyObject_HEAD``是结构的一部分，包含引用计数，类型代码和之前提到的其他部分。

注意这里的区别：C 整数本质上是内存中位置的标签，它的字节编码整数值。Python 整数是指针，指向内存中包含所有 Python 对象信息的位置，包含编码整数值的字节。Python 整数结构中的这些额外信息，允许 Python 自由动态地编码。

然而，Python 类型中的所有这些附加信息都需要付出代价，这在组合了许多这些对象的结构中尤为明显。

### Python 列表不仅仅是列表

现在让我们考虑，当我们使用包含许多 Python 对象的 Python 数据结构时会发生什么。

Python 中的标准可变多元素容器就是列表。我们可以创建一个整数列表，如下所示：

```py
L = list(range(10))
L

# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

type(L[0])

# int
```

或者，类似地，字符串列表：

```py
L2 = [str(c) for c in L]
L2

# ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

type(L2[0])

# str
```

由于 Python 的动态类型，我们甚至可以创建异构列表：

```py
L3 = [True, "2", 3.0, 4]
[type(item) for item in L3]

# [bool, str, float, int]
```

但是这种灵活性需要付出代价：为了允许这些灵活类型，列表中的每个项目都必须包含自己的类型信息，引用计数和其他信息 - 也就是说，每个项目都是完整的 Python 对象。在所有变量属于同一类型的特殊情况下，大部分信息都是冗余的：将数据存储在固定类型数组中会更加高效。

动态类型列表和固定类型（NumPy 样式）数组之间的区别如下图所示：

![Array Memory Layout](../img/9-3-2.png)

在实现级别，数组基本上包含指向一个连续数据块的单个指针。

另一方面，Python 列表包含一个指向指针块的指针，每个指针指向一个完整的 Python 对象，就像我们之前看到的 Python 整数一样。

同样，列表的优点是灵活性：因为每个列表元素是包含数据和类型信息的完整结构，所以列表可以填充为任何所需类型的数据。固定类型的 NumPy 风格数组缺乏这种灵活性，但是对于存储和操作数据更有效。

### Python 中固定类型的数组

Python提供了几种不同的选项，用于在固定类型数据缓冲区中高效存储数据。内置的``array``模块（自 Python 3.3 起可用）可用于创建统一类型的密集数组：

```py
import array
L = list(range(10))
A = array.array('i', L)
A

# array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
```

这里``'i'``是一个类型代码，表示内容是整数。然而，更有用的是 NumPy 包的``ndarray``对象。

虽然Python的``array``对象提供了基于数组的，数据的有效存储，但 NumPy 在数组上添加了高效操作。我们将在后面的章节中探讨这些操作; 在这里，我们将演示创建 NumPy 数组的几种方法。

我们将从别名为``np``的标准 NumPy 导入开始：

```py
import numpy as np
```

### 从 Python 列表创建数组

首先，我们可以使用``np.array``从 Python 列表创建数组：


```py
# 整数数组
np.array([1, 4, 2, 5, 3])

# array([1, 4, 2, 5, 3])
```

请记住，与 Python 列表不同，NumPy 仅限于类型相同的数组。
如果类型不匹配，NumPy 将尽可能向上转换（此处，整数向上转换为浮点数）：

```py
np.array([3.14, 4, 2, 3])

# array([ 3.14,  4.  ,  2.  ,  3.  ])
```

如果我们想显式设置所得数组的数据类型，我们可以使用``dtype``关键字：

```py
np.array([1, 2, 3, 4], dtype='float32')

# array([ 1.,  2.,  3.,  4.], dtype=float32)
```

最后，与 Python 列表不同，NumPy 数组可以是显式多维的; 这是一种方法，使用列表的列表初始化多维数组：

```py
# 嵌套列表产生多维数组
np.array([range(i, i + 3) for i in [2, 4, 6]])

'''
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])
'''
```

内部列表被视为生成的二维数组的行。

### 从零开始创建数组

特别是对于较大的数组，使用 NumPy 中内置的例程从头开始创建数组效率更高。以下是几个例子：

```py
# 创建长度为 10 的零填充的整数数组
np.zeros(10, dtype=int)

# array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])


# 创建一填充的 3x5 浮点数数组
np.ones((3, 5), dtype=float)

'''
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
'''

# 创建 3.14 填充的 3x5 浮点数数组
np.full((3, 5), 3.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]])
'''

# 创建数组，填充为 0 到 20 步长为 2 的线性序列
# （类似于内建的 range() 函数）
np.arange(0, 20, 2)

# array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

# 创建五个值的数组，从 0 到 1 等间隔
np.linspace(0, 1, 5)

# array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ])

# 创建 3x3 数组，包含 0 到 1 均匀分布随机值
np.random.random((3, 3))

'''
array([[ 0.99844933,  0.52183819,  0.22421193],
       [ 0.08007488,  0.45429293,  0.20941444],
       [ 0.14360941,  0.96910973,  0.946117  ]])
'''

# 创建 3x3 数组，包含均值为 0 标准差为 1 的正态分布随机值
np.random.normal(0, 1, (3, 3))

'''
array([[ 1.51772646,  0.39614948, -0.10634696],
       [ 0.25671348,  0.00732722,  0.37783601],
       [ 0.68446945,  0.15926039, -0.70744073]])
'''

# 创建 3x3 数组，包含 [0, 10) 中的随机值
np.random.randint(0, 10, (3, 3))

'''
array([[2, 3, 4],
       [5, 7, 8],
       [0, 5, 0]])
'''

# 创建 3x3 单位矩阵
np.eye(3)

'''
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])
'''

# 创建三个整数的未初始化数组
# 值是内存地址中已经存在的任何东西
np.empty(3)

# array([ 1.,  1.,  1.])
```

### NumPy 标准数据类型

NumPy 数组包含类型单一的值，因此详细了解这些类型及其限制非常重要。由于 NumPy 是用 C 语言构建的，因此 C，Fortran 和其他相关语言的用户会熟悉这些类型。标准 NumPy 数据类型列在下表中。

请注意，在构造数组时，可以使用字符串指定它们：

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

或者使用相关的 NumPy 对象：

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

|   数据类型    |     描述    |
|---------------|-------------|
| ``bool_``     | 布尔值（True 或 False）储存为字节 |
| ``int_``      | 默认整数类型（与 C ``long``相同；通常是``int64``或``int32``）| 
| ``intc``      | 等价于 C ``int``（normally ``int32`` or ``int64``）| 
| ``intp``      | 用于索引的整数（与 C ``ssize_t``相同；通常是``int32``或``int64``）| 
| ``int8``      | 字节（-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 位指数，10 位尾数| 
| ``float32``   | 单精度浮点: 符号位，8 位指数，23 位尾数| 
| ``float64``   | 双精度浮点: 符号位，11 位指数，52 位尾数| 
| ``complex_``  | ``complex128``的简写 | 
| ``complex64`` | 复数，表示为两个 32 位浮点 | 
| ``complex128``| 复数，表示为两个 64 位浮点 | 

更高级的类型规范是可能的，例如指定大或小端编码；更多信息请参阅[ NumPy 文档](http://numpy.org/)。

NumPy 还支持复合数据类型，这将在结构化数据：NumPy 的结构化数组中介绍。