提交 8296d015 编写于 作者: W wizardforcel

2020-07-25 13:03:27

上级 ef390436
......@@ -2,7 +2,7 @@
无论您是科学/分析编程的新手还是经验丰富的专家,这本书都将为您提供成功创建,优化和分发 Python / NumPy 分析模块所需的技能。
从一开始,这本书将涵盖 NumPy 数组的关键功能以及调整数据格式以使其最适合您的分析需求的详细信息。 然后,您将获得各种多维,数据类型的分析所共有的核心和子模块的演练。 接下来,您将继续进行关键技术实现,例如线性代数和傅立叶分析。 最后,您将学习如何使用 Cython 和 NumPy C API 扩展 NumPy 功能的功能和性能。 本书的最后一章还提供了高级材料,可帮助您自己进一步学习。
从一开始,这本书将涵盖 NumPy 数组的关键功能以及调整数据格式以使其最适合您的分析需求的详细信息。 然后,您将获得各种多维,数据类型的分析所共有的核心和子模块的演练。 接下来,您将继续进行关键技术实现,例如线性代数和傅立叶分析。 最后,您将学习如何使用 Cython 和 NumPy C API 扩展 NumPy 的功能和性能。 本书的最后一章还提供了高级材料,可帮助您自己进一步学习。
如果您打算在分析项目中使用 NumPy,则本指南是非常宝贵的教程。
......@@ -12,13 +12,13 @@
第 2 章,“NumPy `ndarray`对象”涵盖了 NumPy `ndarray`对象的基本用法,包括初始化,基本属性,数据类型和内存布局。 它还涵盖了操作下的理论,使您可以清晰地了解`ndarray`
第 3 章,“使用 Numpy 数组”是有关 NumPy `ndarray`使用的高级章节,该章延续了第 2 章 NumPy `ndarray`对象。 它涵盖了 NumPy 中的通用功能,并向您展示了加快代码速度的技巧。 它还显示形状控制和广播规则。
第 3 章,“使用 Numpy 数组”是有关 NumPy `ndarray`使用的高级章节,该章延续了第 2 章 NumPy `ndarray`对象。 它涵盖了 NumPy 中的通用函数,并向您展示了加快代码速度的技巧。 它还显示形状控制和广播规则。
第 4 章,“Numpy 核心和子模块”包括两个部分。 第一部分详细说明了 NumPy `ndarray`分配内存的方式与 CPU 缓存的交互之间的关系。 本章的第二部分介绍了特殊的 NumPy 数组,其中包含多种数据类型(结构/记录数组)。 此外,本章还将探讨 NumPy 中的实验性`datetime64`模块。
第 5 章,“NumPy 中的线性代数”从利用线性代数模块的矩阵和数学计算开始。 它向您展示了解决数学问题的多种方法:使用矩阵,向量分解和多项式。 它还提供了曲线拟合和回归的具体实践。
第 6 章,“NumPy 中的傅立叶分析”涵盖了使用 NumPy FFT 模块进行的信号处理以及傅立叶在放大信号/放大图像时的失真应用。 它还提供了 Python 中 matplotlib 软件包的基本用法。
第 6 章,“NumPy 中的傅立叶分析”涵盖了使用 NumPy FFT 模块进行的信号处理以及傅立叶在放大信号/放大图像时的失真应用。 它还提供了 Python 中 matplotlib 包的基本用法。
第 7 章,“构建和分发 NumPy 代码”涵盖了有关使用 Python 打包和发布代码的基本细节。 它基本介绍了 NumPy 特定的安装文件以及如何构建扩展模块。
......
......@@ -31,9 +31,9 @@ IPython 的主要作者 Fernando Perez 在 2012 年加拿大 PyCon 的主题演
> “科学计算的发展不仅仅是因为软件的发展,而且还因为我们作为科学家所做的不仅仅是浮点运算。”
这正是 SciPy 栈拥有如此丰富的功能的原因。 大多数 SciPy 栈的演进是由试图以通用编程语言解决科学和工程问题的科学家和工程师团队推动的。 对 NumPy 为什么如此重要的一个单方面的解释是,它提供了科学计算中大多数任务所必需的核心多维数组对象。 这就是为什么它是 SciPy 栈的根本原因。 NumPy 使用久经考验的科学库,提供了一种简单的方法来与遗留的 Fortran 和 C/C++ 数字代码对接,我们知道该库已经运行了数十年。 全世界的公司和实验室都使用 Python 将已经存在很长时间的遗留代码粘合在一起。 简而言之,这意味着 NumPy 允许我们站在巨人的肩膀上。 我们不必重新发明轮子。 这是每个其他 SciPy 软件包的依赖项。 NumPy `ndarray`对象实际上是下一章的主题,它是 Pythonic 接口,用于用 Fortran,C 和 C++ 编写的库所使用的数据结构。 实际上,NumPy `ndarray`对象使用的内部内存布局实现 C 和 Fortran 布局。 这将在以后的章节中详细讨论。
这正是 SciPy 栈拥有如此丰富的功能的原因。 大多数 SciPy 栈的演进是由试图以通用编程语言解决科学和工程问题的科学家和工程师团队推动的。 对 NumPy 为什么如此重要的一个单方面的解释是,它提供了科学计算中大多数任务所必需的核心多维数组对象。 这就是为什么它是 SciPy 栈的根本原因。 NumPy 使用久经考验的科学库,提供了一种简单的方法来与遗留的 Fortran 和 C/C++ 数字代码对接,我们知道该库已经运行了数十年。 全世界的公司和实验室都使用 Python 将已经存在很长时间的遗留代码粘合在一起。 简而言之,这意味着 NumPy 允许我们站在巨人的肩膀上。 我们不必重新发明轮子。 这是每个其他 SciPy 包的依赖项。 NumPy `ndarray`对象实际上是下一章的主题,它是 Pythonic 接口,用于用 Fortran,C 和 C++ 编写的库所使用的数据结构。 实际上,NumPy `ndarray`对象使用的内部内存布局实现 C 和 Fortran 布局。 这将在以后的章节中详细讨论。
栈的下一层包括 SciPy,matplotlib,IPython(Python 的交互式外壳;我们将在整本书中将其用作示例,其安装和使用的详细信息将在后面的部分中提供)以及 SymPy 模块。 SciPy 提供了生态系统主要部分所依赖的大部分科学和数字功能。 Matplotlib 是 Python 中的事实绘图和数据可视化库。 IPython 是用于 Python 中科学计算的日益流行的交互式环境。 实际上,该项目已经进行了如此积极的开发并享有很高的知名度,以至于它不再局限于 Python,而且将其功能扩展到其他科学语言,尤其是 R 和 Julia。 栈中的这一层可以看作是 NumPy 的面向核心数组的功能与栈较高层提供的特定于域的抽象之间的桥梁。 这些特定于领域的工具通常称为 SciKits,受欢迎的工具包括 scikit-image(图像处理),scikit-learn(机器学习),statsmodels(统计信息),pandas(高级数据分析)等等。 由于科学的 Python 社区非常活跃,因此几乎不可能在 Python 中列出每个科学软件包,并且针对大量的科学问题总是有很多发展。 跟踪项目的最佳方法是参与社区。 加入邮件列表,编写代码,将软件用于日常计算需求并报告错误,这非常有用。 本书的目标之一是使您足够感兴趣,以积极地参与科学的 Python 社区。
栈的下一层包括 SciPy,matplotlib,IPython(Python 的交互式外壳;我们将在整本书中将其用作示例,其安装和使用的详细信息将在后面的部分中提供)以及 SymPy 模块。 SciPy 提供了生态系统主要部分所依赖的大部分科学和数值功能。 Matplotlib 是 Python 中的事实绘图和数据可视化库。 IPython 是用于 Python 中科学计算的日益流行的交互式环境。 实际上,该项目已经进行了如此积极的开发并享有很高的知名度,以至于它不再局限于 Python,而且将其功能扩展到其他科学语言,尤其是 R 和 Julia。 栈中的这一层可以看作是 NumPy 的面向核心数组的功能与栈较高层提供的特定于域的抽象之间的桥梁。 这些特定于领域的工具通常称为 SciKits,受欢迎的工具包括 scikit-image(图像处理),scikit-learn(机器学习),statsmodels(统计信息),pandas(高级数据分析)等等。 由于科学的 Python 社区非常活跃,因此几乎不可能在 Python 中列出每个科学包,并且针对大量的科学问题总是有很多发展。 跟踪项目的最佳方法是参与社区。 加入邮件列表,编写代码,将软件用于日常计算需求并报告错误,这非常有用。 本书的目标之一是使您足够感兴趣,以积极地参与科学的 Python 社区。
# NumPy 数组的必要性
......@@ -112,11 +112,11 @@ C:\Users\JohnDoe> python hello_world.py
面向 Canopy 用户的注意事项:可以使用 Canopy GUI,该 GUI 包括嵌入式 IPython 控制台,文本编辑器和 IPython Notebook 编辑器。 使用命令行时,为了获得最佳效果,请使用 Canopy 的“工具”菜单中的 **Canopy 终端**
Windows OS 用户注意事项:除了 Python 发行版,您还可以从 [Ghristoph Gohlke 网站](http://www.lfd.uci.edu/~gohlke/pythonlibs/)安装预构建的 Windows python 扩展软件包。
Windows OS 用户注意事项:除了 Python 发行版,您还可以从 [Ghristoph Gohlke 网站](http://www.lfd.uci.edu/~gohlke/pythonlibs/)安装预构建的 Windows python 扩展包。
## 使用 Python 包管理器
您还可以使用以下命令之一使用 Python 软件包管理器(例如 enpkg,Conda,PIP 或 EasyInstall)来安装需求。 将`numpy`替换为您要安装的其他任何软件包名称,例如`ipython``matplotlib`等:
您还可以使用以下命令之一使用 Python 包管理器(例如 enpkg,Conda,PIP 或 EasyInstall)来安装需求。 将`numpy`替换为您要安装的其他任何包名称,例如`ipython``matplotlib`等:
```py
$ pip install numpy
......@@ -126,9 +126,9 @@ $ conda install numpy # for Anaconda users**
```
## 使用本机软件包管理器
## 使用本机包管理器
如果要使用的 Python 解释器随操作系统一起提供,而不是第三方安装,则您可能更喜欢使用特定于操作系统的软件包管理器,例如 aptitude,yum 或 Homebrew。 下表说明了包管理器和用于安装 NumPy 的相应命令:
如果要使用的 Python 解释器随操作系统一起提供,而不是第三方安装,则您可能更喜欢使用特定于操作系统的包管理器,例如 aptitude,yum 或 Homebrew。 下表说明了包管理器和用于安装 NumPy 的相应命令:
| **包管理器** | **命令** |
| --- | --- |
......
......@@ -122,13 +122,13 @@ Name: 1995_COUNT_ALL_TYPES, dtype: object
```
首先,我们将`sale.csv`读入名为`sales``DataFrame`对象; 当我们打印销售的`shapes`时,我们发现数据框中有 384 条记录和 97 列。 `DataFrame column`属性的返回列表是一个普通的 Python 列表,我们在数据中打印了前三列:`LA_Code``LA_Name``1995_COUNT_ALL_TYPES`。 然后,我们使用`head()`功能`1995_COUNT_ALL_TYPES`中打印了前五个记录(`tail()`函数将打印后五个记录)。
首先,我们将`sale.csv`读入名为`sales``DataFrame`对象; 当我们打印销售的`shapes`时,我们发现数据框中有 384 条记录和 97 列。 `DataFrame column`属性的返回列表是一个普通的 Python 列表,我们在数据中打印了前三列:`LA_Code``LA_Name``1995_COUNT_ALL_TYPES`。 然后,我们使用`head()`函数`1995_COUNT_ALL_TYPES`中打印了前五个记录(`tail()`函数将打印后五个记录)。
同样,pandas 是 Python 中一个功能强大的预处理模块(通常,其数据处理能力超过其预处理功能,但在前面的示例中,我们仅介绍了预处理部分),并且它具有许多方便的功能来帮助您清理数据并准备数据。 您的分析。 本节仅作介绍; 由于空间限制,我们无法涵盖很多内容,例如数据透视,`datetime`等。 希望您能理解并开始提高脚本的效率。
# scikit-learn
Scikit 是 SciPy 工具包的缩写,它是 SciPy 的附加软件包。 它提供了广泛的分析模块,而 scikit-learn 是其中之一。 这是迄今为止最全面的 Python 机器学习模块。 scikit-learn 提供了一种简单有效的方法来执行数据挖掘和数据分析,并且它拥有非常活跃的用户社区。
Scikit 是 SciPy 工具包的缩写,它是 SciPy 的附加包。 它提供了广泛的分析模块,而 scikit-learn 是其中之一。 这是迄今为止最全面的 Python 机器学习模块。 scikit-learn 提供了一种简单有效的方法来执行数据挖掘和数据分析,并且它拥有非常活跃的用户社区。
您可以[从 scikit-learn 的官方网站下载并安装它](http://scikit-learn.org/stable/)。 如果您使用的是 Python 科学发行版(例如 Anaconda),则也包含在其中。
......
......@@ -28,7 +28,7 @@ from numpy import *
```
因为它可能会覆盖全局名称空间中已经存在的许多功能,所以不建议这样做。 这可能会导致您的代码出现意外行为,并可能在其中引入非常细小的错误。 这也可能会在代码本身中造成冲突(例如 numPy 具有`any`并会与系统`any`关键字发生冲突),并可能在检查或调试一段代码时引起混乱。 因此,重要的是建议始终使用带有显式名称的导入 numPy,例如第一行中使用的`np`约定:-`import numpy as np`,这是用于导入目的的标准约定,因为它有助于开发人员找出功能的来源。 这可以避免大型程序中的许多混乱。
因为它可能会覆盖全局名称空间中已经存在的许多函数,所以不建议这样做。 这可能会导致您的代码出现意外行为,并可能在其中引入非常细小的错误。 这也可能会在代码本身中造成冲突(例如 numPy 具有`any`并会与系统`any`关键字发生冲突),并可能在检查或调试一段代码时引起混乱。 因此,重要的是建议始终使用带有显式名称的导入 numPy,例如第一行中使用的`np`约定:-`import numpy as np`,这是用于导入目的的标准约定,因为它有助于开发人员找出函数的来源。 这可以避免大型程序中的许多混乱。
如我们将看到的,可以用多种方式创建 NumPy 数组。 创建数组的最简单方法之一是使用`array`函数。 注意,我们向函数传递了一个列表列表,组成列表的长度相等。 每个组成列表成为数组中的一行,并且这些列表的元素填充了结果数组的列。 `array`函数可以在列表甚至嵌套列表上调用。 由于此处输入的嵌套级别是 2,因此生成的数组是二维的。 这意味着可以使用两个整数集对数组进行索引。 计算数组维数的最简单方法是检查数组的`ndim`属性:
......@@ -288,7 +288,7 @@ In [35]: y = np.array(x)
```
NumPy 具有称为`arange`的便捷功能,该功能结合了`range``array`功能的功能。 前两行代码与此等效:
NumPy 具有称为`arange`的便捷函数,该函数结合了`range``array`函数的功能。 前两行代码与此等效:
```py
In [36]: x = np.arange(5)
......@@ -312,13 +312,13 @@ Out[39]: (2, 3)
## 创建随机数组
NumPy 中的`random`模块提供了各种功能来创建任何数据类型的随机数组。 在整本书中,我们将非常频繁地使用此模块来演示 NumPy 中函数的工作。 `random`模块大致由以下功能组成:
NumPy 中的`random`模块提供了各种函数来创建任何数据类型的随机数组。 在整本书中,我们将非常频繁地使用此模块来演示 NumPy 中函数的工作。 `random`模块大致由以下函数组成:
* 创建随机数组
* 创建数组的随机排列
* 生成具有特定概率分布的数组
我们将在本书中详细介绍所有这些内容。 在本章中,我们将重点介绍`random`模块中的两个重要功能-`rand``random`。 这是一个简单的代码段,展示了这两个功能的使用:
我们将在本书中详细介绍所有这些内容。 在本章中,我们将重点介绍`random`模块中的两个重要函数-`rand``random`。 这是一个简单的代码段,展示了这两个函数的使用:
```py
In [40]: x = np.random.rand(2, 2, 2)
......@@ -335,9 +335,9 @@ Out[44]: (2, 3, 4)
```
注意传递给两个函数的参数之间的细微差别。 随机函数接受*元组*作为参数,并创建维数等于元组长度的数组。 各个尺寸的长度等于元组的元素。 另一方面,`rand`函数采用任意数量的*整数参数*,并返回一个随机数组,使得其维数等于传递给该函数的整数参数的数量 ,并且各个维度的长度等于整数参数的值。 因此,前面的代码段中的`x`是三维数组(传递给函数的参数数量),并且`x`的三个维度中的每个维度的长度均为`2`(每个参数的值)。 `rand``random`的便捷功能。 这两个函数可以互换使用,只要传递的参数分别对两个函数均有效即可。
注意传递给两个函数的参数之间的细微差别。 随机函数接受*元组*作为参数,并创建维数等于元组长度的数组。 各个尺寸的长度等于元组的元素。 另一方面,`rand`函数采用任意数量的*整数参数*,并返回一个随机数组,使得其维数等于传递给该函数的整数参数的数量 ,并且各个维度的长度等于整数参数的值。 因此,前面的代码段中的`x`是三维数组(传递给函数的参数数量),并且`x`的三个维度中的每个维度的长度均为`2`(每个参数的值)。 `rand``random`的便捷函数。 这两个函数可以互换使用,只要传递的参数分别对两个函数均有效即可。
但是,这两个函数的主要缺点是-它们只能创建浮点数组。 如果我们想要一个随机整数数组,则必须将这些函数的输出转换为整数。 但是,这也是一个重大问题,因为 NumPy 的`int`函数将浮点数截断为最接近 0 的整数(这与`floor`函数等效)。 因此,将`rand``random`的输出强制转换为整数将始终返回零数组,因为这两个函数都返回`[0, 1)`区间内的浮点数。 可以使用`randint`功能解决此问题,如下所示:
但是,这两个函数的主要缺点是-它们只能创建浮点数组。 如果我们想要一个随机整数数组,则必须将这些函数的输出转换为整数。 但是,这也是一个重大问题,因为 NumPy 的`int`函数将浮点数截断为最接近 0 的整数(这与`floor`函数等效)。 因此,将`rand``random`的输出强制转换为整数将始终返回零数组,因为这两个函数都返回`[0, 1)`区间内的浮点数。 可以使用`randint`函数解决此问题,如下所示:
```py
In [45]: LOW, HIGH = 1, 11
......@@ -353,7 +353,7 @@ Out[48]: [ 6 9 10 7 9 5 8 8 9 3]
`randint`函数带有三个参数,其中两个是可选的。 第一个参数表示输出值的期望下限,第二个可选参数表示输出值的(专有)上限。 可选的`size`参数是一个元组,用于确定输出数组的形状。
还有许多其他功能,例如将随机数生成器植入随机子模块中。 有关详细信息,请参考[这里](http://docs.scipy.org/doc/numpy/reference/routines.random.html)
还有许多其他函数,例如将随机数生成器植入随机子模块中。 有关详细信息,请参考[这里](http://docs.scipy.org/doc/numpy/reference/routines.random.html)
## 其他数组
......
......@@ -86,7 +86,7 @@ Out[20]: dtype('int32')
NumPy 具有许多通用函数(所谓的`ufunc`),因此可以利用它们来发挥自己的优势,从而尽可能地减少循环以优化代码。 `ufunc`在数学,三角函数,汇总统计信息和比较运算方面有很好的覆盖范围。 有关详细的`ufunc`列表,请参考[在线文档](http://docs.scipy.org/doc/numpy/reference/ufuncs.html)
由于 NumPy 中有大量的`ufunc`,我们很难在一章中涵盖所有这些功能。 在本节中,我们仅旨在了解如何以及为何应使用 NumPy ufuncs。
由于 NumPy 中有大量的`ufunc`,我们很难在一章中涵盖所有这些函数。 在本节中,我们仅旨在了解如何以及为何应使用 NumPy ufuncs。
## 基本`ufunc`入门
......@@ -120,7 +120,7 @@ Out[26]: 5
如您所见,`numpy.minimum()`比较两个数组并返回两个数组的最小值。 `1` 是数组值的形状,其值为 7,因此将其广播到`[7, 7, 7, 7, 7]`。 我们将在下一节中讨论 NumPy 广播规则。 `numpy.min()`,仅接受一个必需的参数,并返回数组中最小的元素。
## 使用更高级的功能
## 使用更高级的函数
大多数`ufunc`具有可选参数,以在使用它们时提供更大的灵活性。 以下示例将使用`numpy.median()`。 这是在`numpy.repeat()`函数创建的二维数组上使用可选的`axis` 参数完成的,以重复`x`数组三次并将其分配给`z`变量:
......@@ -165,7 +165,7 @@ array([[25, 30, 35, 40, 45],
```
如果您需要更高级的功能,则可以考虑构建自己的`ufunc`,这可能需要使用 Python-C API,或者您也可以使用 Numba 模块(向量化装饰器)来实现自定义的`ufunc`。 在本章中,我们的目标是了解 NumPy `ufunc`,因此我们将不介绍自定义的`ufunc`。 有关更多详细信息,请参阅 NumPy 的联机文档,名为[编写自己的`ufunc`](http://docs.scipy.org/doc/numpy/user/c-info.ufunc-tutorial.html)或 Numba 文档,[创建 Numpy 通用函数](http://numba.pydata.org/numba-doc/dev/user/vectorize.html)
如果您需要更高级的函数,则可以考虑构建自己的`ufunc`,这可能需要使用 Python-C API,或者您也可以使用 Numba 模块(向量化装饰器)来实现自定义的`ufunc`。 在本章中,我们的目标是了解 NumPy `ufunc`,因此我们将不介绍自定义的`ufunc`。 有关更多详细信息,请参阅 NumPy 的联机文档,名为[编写自己的`ufunc`](http://docs.scipy.org/doc/numpy/user/c-info.ufunc-tutorial.html)或 Numba 文档,[创建 Numpy 通用函数](http://numba.pydata.org/numba-doc/dev/user/vectorize.html)
# 广播和形状操作
......@@ -266,7 +266,7 @@ In [50]: %timeit x.ravel()
```
前面的示例是将`100 x 100 x 100`数组整形为一个尺寸; 在这里,我们应用`numpy.flatten()``numpy.ravel()`这两个函数来折叠数组,同时我们还比较了执行时间。 我们注意到`numpy.flatten()``numpy.ravel()`之间的速度差异很大,但是它们都比三层 Python 循环快得多。 两种功能在性能上的差异是`np.flatten()`从原始数组创建副本,而`np.ravel()`只是更改视图(如果您不记得副本和视图之间的区别,请回到第 2 章, “NumPy `ndarray`对象”)。
前面的示例是将`100 x 100 x 100`数组整形为一个尺寸; 在这里,我们应用`numpy.flatten()``numpy.ravel()`这两个函数来折叠数组,同时我们还比较了执行时间。 我们注意到`numpy.flatten()``numpy.ravel()`之间的速度差异很大,但是它们都比三层 Python 循环快得多。 两个函数在性能上的差异是`np.flatten()`从原始数组创建副本,而`np.ravel()`只是更改视图(如果您不记得副本和视图之间的区别,请回到第 2 章, “NumPy `ndarray`对象”)。
这个例子只是向您展示了 NumPy 提供了许多功能,其中一些可以产生相同的结果。 选择满足您目的的功能,同时为您提供优化的性能。
......
......@@ -392,7 +392,7 @@ dtype = [('id', '<i4'), ('value', '<f4'), ('date', 'S10'), ('mask','i1')])
```
仅当直接导入`numpy.lib.recfunctions`且模块中具有`append_field()`功能时,才能访问它。 追加一个记录数组就像追加一个 NumPy 数组一样简单:第一个参数是基本数组;第二个参数是基本数组。 第二个参数是新字段名称`mask`以及与之关联的数据; 最后一个参数是数据类型。 由于掩码是布尔数组,因此 NumPy 会自动将掩码应用于记录数组,但是我们仍然可以看到在`read_array`中添加了一个新字段,掩码的值反映了阈值(`>= 0.75``value`字段。 这只是向您展示如何将 NumPy 数组与数据文件连接的开始。 现在是时候对您的数据进行一些真实的分析了!
仅当直接导入`numpy.lib.recfunctions`且模块中具有`append_field()`函数时,才能访问它。 追加一个记录数组就像追加一个 NumPy 数组一样简单:第一个参数是基本数组;第二个参数是基本数组。 第二个参数是新字段名称`mask`以及与之关联的数据; 最后一个参数是数据类型。 由于掩码是布尔数组,因此 NumPy 会自动将掩码应用于记录数组,但是我们仍然可以看到在`read_array`中添加了一个新字段,掩码的值反映了阈值(`>= 0.75``value`字段。 这只是向您展示如何将 NumPy 数组与数据文件连接的开始。 现在是时候对您的数据进行一些真实的分析了!
# 总结
......
......@@ -199,7 +199,7 @@ Out[34]: array([ 0, 0, -1])
![Linear algebra in NumPy](img/00013.jpeg)
NumPy 为标准向量例程提供了以前的功能。 现在,我们将讨论本节的关键主题:用于线性代数的`numpy.linalg`子模块。 将 NumPy `ndarray``numpy.linalg`结合使用会比`numpy.matrix()`更好。
NumPy 为标准向量例程提供了前面的功能。 现在,我们将讨论本节的关键主题:用于线性代数的`numpy.linalg`子模块。 将 NumPy `ndarray``numpy.linalg`结合使用会比`numpy.matrix()`更好。
### 注意
......@@ -240,7 +240,7 @@ matrix([[ 0.2667],
```
`numpy.linalg.solve(A,b)`计算`x`的解,其中第一个输入参数(`A`)代表系数数组,第二个参数(`b`)代表坐标或因变量值。 `numpy.linalg.solve()`功能支持输入数据类型。 在示例中,我们使用矩阵作为输入,因此输出还返回一个矩阵`x`。 我们也可以使用`ndarray `作为输入。
`numpy.linalg.solve(A,b)`计算`x`的解,其中第一个输入参数(`A`)代表系数数组,第二个参数(`b`)代表坐标或因变量值。 `numpy.linalg.solve()`函数支持输入数据类型。 在示例中,我们使用矩阵作为输入,因此输出还返回一个矩阵`x`。 我们也可以使用`ndarray `作为输入。
使用 NumPy 进行线性代数运算时,最好仅使用一种数据类型,即`ndarray``matrix`。 不建议在计算中使用混合类型。 原因之一是减少了不同数据类型之间的转换。 另一个原因是要避免两种类型的计算中的意外错误。 由于`ndarray`对数据尺寸的限制较少,并且可以执行所有类似矩阵的运算,因此与`matrix`相比,`ndarray``numpy.linalg`结合使用是首选的。
......@@ -361,7 +361,7 @@ NumPy 还提供了使用多项式的方法,并包括一个名为`numpy.polynom
正如我们在矩阵类部分所述,将`ndarray`与 NumPy 函数结合使用是首选,因为`ndarray`可以在任何函数中接受,而矩阵和多项式对象则需要转换,尤其是在与其他程序通信时。 它们都提供了方便的属性,但是在大多数情况下,`ndarray`足够好。
在本节中,我们将介绍如何基于一组根来计算系数,以及如何求解多项式方程,最后我们将评估积分和导数。 让我们从计算多项式的系数开始:
在本节中,我们将介绍如何基于一组根来计算系数,以及如何求解多项式方程,最后我们将求值积分和导数。 让我们从计算多项式的系数开始:
```py
In [66]: root = np.array([1,2,3,4])
......@@ -388,7 +388,7 @@ Out[69]: 24
```
`numpy.polyval()`具有两个输入参数,第一个是多项式的系数数组,第二个是用于评估给定多项式的特定点值。 我们也可以输入`x`的序列,结果将返回`ndarray`,其值对应于给定的`x`序列。
`numpy.polyval()`具有两个输入参数,第一个是多项式的系数数组,第二个是用于求值给定多项式的特定点值。 我们也可以输入`x`的序列,结果将返回`ndarray`,其值对应于给定的`x`序列。
接下来我们将讨论**积分****导数**。 我们将继续以`x^4 - 10x^3 + 35x^2 - 50x + 24`的示例为例:
......@@ -434,7 +434,7 @@ Out[79]: array([ 0.25 , 0.3333, 0.5 , 1\. ])
```
使用`Polynomial`实例,我们可以简单地调用`coef`属性以显示系数的`ndarray``roots()`方法将显示根。 接下来,我们将评估特定值的多项式`5`
使用`Polynomial`实例,我们可以简单地调用`coef`属性以显示系数的`ndarray``roots()`方法将显示根。 接下来,我们将求值特定值的多项式`5`
```py
In [80]: polynomial.polyval(p, 5)
......
......@@ -19,7 +19,7 @@ Matplotlib 是我们将在本章中使用的可视化模块。 [请从官方网
![Before we start](img/00017.jpeg)
上图显示原始功能(信号),下图显示傅立叶变换。 请在 IPython 命令提示符下键入以下代码,或将其保存到`.py`文件并将其加载到提示符:
上图显示原始函数(信号),下图显示傅立叶变换。 请在 IPython 命令提示符下键入以下代码,或将其保存到`.py`文件并将其加载到提示符:
```py
#### The Plotting Functions ####import matplotlib.pyplot as plt
......@@ -45,7 +45,7 @@ def show(ori_func, ft, sampling_period = 5):
# 信号处理
在本节中,我们将使用 NumPy 函数来模拟多个信号函数并将其转换为傅立叶变换。 我们将重点介绍`numpy.fft`及其相关功能。 我们希望在本节之后,您将对在 NumPy 中使用傅立叶变换有所了解。 理论部分将在下一部分中介绍。
在本节中,我们将使用 NumPy 函数来模拟多个信号函数并将其转换为傅立叶变换。 我们将重点介绍`numpy.fft`及其相关函数。 我们希望在本节之后,您将对在 NumPy 中使用傅立叶变换有所了解。 理论部分将在下一部分中介绍。
我们要使用的第一个示例是心跳信号,它是一系列正弦波。 频率为每分钟 60 次(1Hz),我们的采样周期为 5 秒长,采样间隔为 0.005 秒。 首先创建正弦波:
......@@ -136,7 +136,7 @@ In [24]: show(x, y)
![Fourier analysis](img/00023.jpeg)
`A[k]`代表离散傅里叶变换,`a[m]`代表原始功能`a[m] -> A[k]`的转换是从配置空间到频率空间的转换。 让我们手动计算此方程,以更好地了解转换过程。 我们将使用具有 500 个值的随机信号:
`A[k]`代表离散傅里叶变换,`a[m]`代表原始函数`a[m] -> A[k]`的转换是从配置空间到频率空间的转换。 让我们手动计算此方程,以更好地了解转换过程。 我们将使用具有 500 个值的随机信号:
```py
In [25]: x = np.random.random(500)
......@@ -360,6 +360,6 @@ In [74]: plt.show()
在本章中,我们介绍了一维和多维傅立叶变换的用法以及它们在信号处理中的应用方式。 现在,您了解了 NumPy 中离散傅立叶变换的实现,并且我们在手动实现的脚本与 NumPy 内置模块之间进行了性能比较。
我们还完成了图像插值的实际应用,并且由于了解`matplotlib`软件包的一些基础知识而获得了加号。
我们还完成了图像插值的实际应用,并且由于了解`matplotlib`包的一些基础知识而获得了加号。
在下一章中,我们将看到如何使用`numpy.distutils()`子模块分发代码。
\ No newline at end of file
# 七、构建和分发 NumPy 代码
在现实世界中,您将编写一个应用,以将其分发到世界上或在其他各种计算机上重用。 为此,您希望应用以标准方式打包,以便社区中的每个人都能理解和遵循。 正如您现在已经注意到的那样,Python 用户主要使用名为`pip`的包管理器来自动安装其他程序员创建的模块。 Python 具有一个称为 **PyPI****Python 软件包索引**)的打包平台,该平台是 50,000 多个 Python 软件包的官方中央存储库。 一旦在 PyPi(又名 *Cheese Shop*)中注册了软件包,世界各地的其他用户都可以在使用`pip`等软件包管理系统对其进行配置后进行安装。 Python 随附了许多解决方案,可帮助您构建代码以准备分发给 *Cheese Shop* ,并且在本章中,我们将重点介绍两个此类工具,`setuptools``Distutils` 除了这两个工具之外,我们还将研究 NumPy 提供的称为`numpy.distutils`的特定模块。 该模块使程序员更容易构建和分发特定于 NumPy 的代码。 该模块还提供了其他功能,例如用于编译 Fortran 代码,调用`f2py,`等的方法。 在本章中,我们将通过以下步骤来学习包装工作流程:
在现实世界中,您将编写一个应用,以将其分发到世界上或在其他各种计算机上重用。 为此,您希望应用以标准方式打包,以便社区中的每个人都能理解和遵循。 正如您现在已经注意到的那样,Python 用户主要使用名为`pip`的包管理器来自动安装其他程序员创建的模块。 Python 具有一个称为 **PyPI****Python 包索引**)的打包平台,该平台是 50,000 多个 Python 包的官方中央存储库。 一旦在 PyPi(又名 *Cheese Shop*)中注册了包,世界各地的其他用户都可以在使用`pip`等包管理系统对其进行配置后进行安装。 Python 随附了许多解决方案,可帮助您构建代码以准备分发给 *Cheese Shop* ,并且在本章中,我们将重点介绍两个此类工具,`setuptools``Distutils` 除了这两个工具之外,我们还将研究 NumPy 提供的称为`numpy.distutils`的特定模块。 该模块使程序员更容易构建和分发特定于 NumPy 的代码。 该模块还提供了其他函数,例如用于编译 Fortran 代码,调用`f2py`方法。 在本章中,我们将通过以下步骤来学习包装工作流程:
* 我们将建立一个小的但可行的设置
* 我们将说明将 NumPy 模块集成到您的设置中的步骤
......@@ -8,7 +8,7 @@
# Distutils 和 Setuptools 简介
在开始之前,首先让我们了解这些工具是什么以及为什么我们偏爱另一个工具。 `Distutils`是 Python 默认提供的框架,`setuptools`建立在标准`Distutils`的基础上,以提供增强的功能和特性。 在现实世界中,您将永远不会使用`Distutils`。 您可能想单独使用`Distutils`的唯一情况是`setuptools`不可用。 (良好的设置脚本应在继续之前检查`setuptools`的可用性。)在大多数情况下,用户最好安装`setuptools`,因为当今大多数软件包都是基于它们构建的。 在接下来的章节中,我们将使用`setuptools`来构建 Cython 代码; 因此,出于我们的目的,我们现在将安装`setuptools`并从现在开始广泛使用它。
在开始之前,首先让我们了解这些工具是什么以及为什么我们偏爱另一个工具。 `Distutils`是 Python 默认提供的框架,`setuptools`建立在标准`Distutils`的基础上,以提供增强的功能和特性。 在现实世界中,您将永远不会使用`Distutils`。 您可能想单独使用`Distutils`的唯一情况是`setuptools`不可用。 (良好的设置脚本应在继续之前检查`setuptools`的可用性。)在大多数情况下,用户最好安装`setuptools`,因为当今大多数包都是基于它们构建的。 在接下来的章节中,我们将使用`setuptools`来构建 Cython 代码; 因此,出于我们的目的,我们现在将安装`setuptools`并从现在开始广泛使用它。
接下来,让我们从安装必需的工具开始,以构建我们的第一个虚拟(但有效)安装程序。 安装程序正常运行后,我们将在 Pandas 脚本模块的真实脚本中深入介绍 NumPy 的更多功能。 我们将研究脚本中进行的检查,以使其更强大,以及在发生故障时如何提供更多信息。
......@@ -32,7 +32,7 @@
# 建立第一个有效的发行版
我们前面提到的所有工具(`setuptools``Distutils``numpy.distutils`)都围绕功能设置。 为了了解大多数包装要求,我们将研究一个简单的设置功能,然后研究一个成熟的安装程序。 要创建基本的安装程序,我们需要使用有关包的元数据调用安装程序功能。 让我们叫第一个包`py_hello`,它只有一个功能`greeter`,并且在调用时只打印一条消息。 可从 [Bitbucket 存储库下载该包](https://bitbucket.org/tdatta/books/src/af376df081ef/python/simple_setup/?at=master)。该项目的目录结构如下:
我们前面提到的所有工具(`setuptools``Distutils``numpy.distutils`)都围绕`setup`函数。 为了了解大多数包装要求,我们将研究一个简单的`setup`函数,然后研究一个成熟的安装程序。 要创建基本的安装程序,我们需要使用有关包的元数据调用`setup`函数。 让我们叫第一个包`py_hello`,它只有一个函数`greeter`,并且在调用时只打印一条消息。 可从 [Bitbucket 存储库下载该包](https://bitbucket.org/tdatta/books/src/af376df081ef/python/simple_setup/?at=master)。该项目的目录结构如下:
```py
py_hello
......@@ -93,21 +93,21 @@ setup(
以下是安装程序中使用的选项:
* `name` - 这是安装 TAR 归档文件的名称。
* `packages` - 这是一个列出要包含的软件包的列表。
* `scripts` - 这是要安装到`/usr/bin.`等标准位置的脚本的列表。在此特定情况下,仅存在一个 echo 脚本。 这样做的目的是向读者展示如何将软件包附带脚本。
* `packages` - 这是一个列出要包含的包的列表。
* `scripts` - 这是要安装到`/usr/bin.`等标准位置的脚本的列表。在此特定情况下,仅存在一个 echo 脚本。 这样做的目的是向读者展示如何将包附带脚本。
* `package_data` - 这是字典,具有与文件列表相关联的键(包)。
* `version` - 这是您的项目的版本。 这将附加到安装程序名称的末尾。
* `long_description` - 在 PyPI 网站上显示时,它将转换为 HTML。 它应该包含有关您的项目打算提供的信息。 您可以直接在脚本中编写它; 但是,最佳实践是维护`README`文件并从此处读取说明。
* `install_required` - 这是用于添加安装依赖的列表。 您将添加代码中使用的第三方模块的名称和版本。 请注意遵循约定以在此处指定版本。
* `classifiers` - 当您在 PyPI 网站上上传软件包时,将选中此选项。 [您应该从以下网站提供的选项中进行选择](https://pypi.python.org/pypi?:action=list_classifiers)
* `install_required` - 这是用于添加安装依赖的列表。 您将添加代码中使用的第三方模块的名称和版本。 请注意遵循约定以在此处指定版本。
* `classifiers` - 当您在 PyPI 网站上上传包时,将选中此选项。 [您应该从以下网站提供的选项中进行选择](https://pypi.python.org/pypi?:action=list_classifiers)
现在,使用`build`选项运行`setup.py`应该不会给您任何错误,并生成带有`.egg-info`后缀的文件夹。 此时,您可以使用`sdist`选项运行`setup.py`,并创建一个可以与世界共享的软件包。
现在,使用`build`选项运行`setup.py`应该不会给您任何错误,并生成带有`.egg-info`后缀的文件夹。 此时,您可以使用`sdist`选项运行`setup.py`,并创建一个可以与世界共享的包。
您应该看到最终消息为**创建 tar 归档文件**,如下所示:
![Building the first working distribution](img/00032.jpeg)
要测试该软件包,可以按照以下步骤将其安装在本地计算机上:
要测试该包,可以按照以下步骤将其安装在本地计算机上:
```py
python setup.py install
......@@ -201,7 +201,7 @@ if __name__ == "__main__":
上面的脚本从完整的工作设置中删除,着重于几乎所有设置脚本中都可以找到的某些方面。 这些任务确保您已完成足够的错误处理,并且脚本在不解释/提示下一步操作的情况下不会失败:
1. 检查是否已安装 NumPy。 此处用来确保已安装 NumPy 的模式是一种标准模式,您可以将其用于计划使用的所有模块,并且是安装程序所必需的。 为了执行此任务,我们首先构建一个函数`is_numpy_installed`尝试导入`numpy`并返回一个布尔值。 您可能会为安装文件可能使用的所有外部软件包创建类似的功能。 高级用户可以使用 Python 装饰器来以更优雅的方式进行处理。 如果此函数返回错误值,则安装程序应输出警告/信息,以防没有此软件包无法完成安装。
1. 检查是否已安装 NumPy。 此处用来确保已安装 NumPy 的模式是一种标准模式,您可以将其用于计划使用的所有模块,并且是安装程序所必需的。 为了执行此任务,我们首先构建一个函数`is_numpy_installed`尝试导入`numpy`并返回一个布尔值。 您可能会为安装文件可能使用的所有外部包创建类似的函数。 高级用户可以使用 Python 装饰器来以更优雅的方式进行处理。 如果此函数返回错误值,则安装程序应输出警告/信息,以防没有此包无法完成安装。
2.`Extensions`添加到设置文件中。
3. `Extension`类是使我们能够向安装程序中添加非 Python 代码的对象。 `sources`参数可能包含 Fortran 源文件列表。 但是,列表源可能最多包含一个`f2py`签名文件,然后扩展模块的名称必须与签名文件中使用的`<module>`匹配。 `f2py`签名文件必须恰好包含一个 Python 模块块,否则安装程序将无法构建。 您可以决定不在`sources`参数中添加签名文件。 在这种情况下,`f2py`将扫描 Fortran 源文件以获取常规签名,以构造 Fortran 代码的包装器。 可以使用`Extension`类参数`f2py_options`来指定`f2py`进程的其他选项。 这些选项不在本书的讨论范围内,大多数读者不会使用它们。 有关更多详细信息,用户可以参考`api`文档中的`numpy.distutils`扩展类。
......@@ -216,7 +216,7 @@ if __name__ == "__main__":
# 测试您的包
非常重要的一点是,所构建的软件包可以在用户的​​计算机上正常运行/安装。 因此,您应该花时间测试软件包。 测试安装背后的总体思路是创建一个 VirtualEnv 并尝试安装该软件包或完全使用另一个系统。 在此阶段遇到的任何错误都应删除,并且作者应尝试确保更容易遵循这些异常。 异常也应尝试提供解决方案。 此阶段的常见错误是:
非常重要的一点是,所构建的包可以在用户的​​计算机上正常运行/安装。 因此,您应该花时间测试包。 测试安装背后的总体思路是创建一个 VirtualEnv 并尝试安装该包或完全使用另一个系统。 在此阶段遇到的任何错误都应删除,并且作者应尝试确保更容易遵循这些异常。 异常也应尝试提供解决方案。 此阶段的常见错误是:
* 关于预装模块和库的假设。
* 开发人员可能会忘记在安装文件中包含依赖项。 如果使用新的 VirtualEnv 来测试安装程序,则会捕获此错误。
......@@ -225,7 +225,7 @@ if __name__ == "__main__":
# 分发您的应用
完成模块/应用的所有开发并准备好完整的正常工作的应用和设置文件后,下一个任务就是与世界分享您的辛勤工作,使他人受益。 使用 PyPI 将其发布到全世界的步骤非常简单。 作为软件包作者,您需要做的第一件事就是注册自己。 您可以直接从命令行执行以下操作:
完成模块/应用的所有开发并准备好完整的正常工作的应用和设置文件后,下一个任务就是与世界分享您的辛勤工作,使他人受益。 使用 PyPI 将其发布到全世界的步骤非常简单。 作为包作者,您需要做的第一件事就是注册自己。 您可以直接从命令行执行以下操作:
```py
**$ python setup.py register
......@@ -256,4 +256,4 @@ if __name__ == "__main__":
# 总结
在本章中,我们介绍了用于打包和分发应用的工具。 我们首先看了一个更简单的`setup.py`文件。 您研究了功能设置的属性以及这些参数如何链接到最终安装程序。 接下来,我们添加了与 NumPy 相关的代码,并添加了一些异常处理代码。 最后,我们构建了安装程序并学习了如何在 *Cheese Shop*(PyPI 网站)上上传它。 在下一章中,您将研究通过将 Python 代码的一部分转换为 Cython 来进一步加速 Python 代码的方法。
\ No newline at end of file
在本章中,我们介绍了用于打包和分发应用的工具。 我们首先看了一个更简单的`setup.py`文件。 您研究了`setup`函数的属性以及这些参数如何链接到最终安装程序。 接下来,我们添加了与 NumPy 相关的代码,并添加了一些异常处理代码。 最后,我们构建了安装程序并学习了如何在 *Cheese Shop*(PyPI 网站)上上传它。 在下一章中,您将研究通过将 Python 代码的一部分转换为 Cython 来进一步加速 Python 代码的方法。
\ No newline at end of file
......@@ -102,7 +102,7 @@ cdefint a, b, intermediate, x
尽管在此示例代码中提高速度非常重要,但这不是您将遇到的实际代码,因此您应始终记住首先在代码上运行分析器并确定需要优化的部分。 同样,在使用 Cython 时,开发人员应考虑在使用静态类型和灵活性之间进行权衡。 使用类型会降低灵活性,有时甚至会降低可读性。
通过删除`xrange`并改用`for`循环,可以进一步改进此代码。 当您对模块的所有组件/功能都满意并且没有错误后,用户可以将这些功能/过程存储在扩展名为`.pyx`的文件中。 这是 Cython 使用的扩展名。 将此代码与您的应用集成的下一步是在安装文件中添加信息。
通过删除`xrange`并改用`for`循环,可以进一步改进此代码。 当您对模块的所有组件/功能都满意并且没有错误后,用户可以将这些函数/过程存储在扩展名为`.pyx`的文件中。 这是 Cython 使用的扩展名。 将此代码与您的应用集成的下一步是在安装文件中添加信息。
在这里,出于说明目的,我们将代码存储在名为`fib.pyx`的文件中,并创建了一个构建该模块的安装文件:
......
......@@ -17,7 +17,7 @@ NumPy 是一个通用库,旨在满足科学应用开发人员的大多数需
用 C/C++ 编写函数可以为开发人员提供灵活性,以利用这些语言提供的一些高级库。 但是,就必须在解析输入周围编写太多样板代码以构造返回值而言,代价显而易见。 此外,开发人员在引用/解引用对象时必须格外小心,因为这最终可能会导致讨厌的错误和内存泄漏。 随着 C-API 的不断发展,还存在代码未来兼容性的问题。 因此,如果开发人员想要迁移到更高版本的 Python,则他们可能需要为这些基于 C-API 的扩展进行大量维护工作。 由于这些困难,大多数开发人员选择尝试其他优化技术。 (例如 Cython 或 F2PY),然后再探索这条路径。 但是,在某些情况下,您可能想重用 C/C++ 中的其他现有库,这可能适合您的特定目的。 在这些情况下,最好为现有函数编写包装并公开 Python 项目。
接下来,我们将看一些示例代码,并在本章继续介绍时解释其关键功能和宏。 此处提供的代码与 Python 2.X 版本兼容,可能不适用于 Python 3.X。 但是,转换过程应该相似。
接下来,我们将看一些示例代码,并在本章继续介绍时解释其关键函数和宏。 此处提供的代码与 Python 2.X 版本兼容,可能不适用于 Python 3.X。 但是,转换过程应该相似。
### 提示
......@@ -28,7 +28,7 @@ NumPy 是一个通用库,旨在满足科学应用开发人员的大多数需
用 C 编写的扩展模块将包含以下部分:
* 标头段,其中包含所有外部库和`Python.h`
* 初始化段,您可以在其中定义模块名称和 C 模块中的功能
* 初始化段,您可以在其中定义模块名称和 C 模块中的函数
* 方法结构数组,用于定义模块中的所有函数
* 一个实现部分,您在其中定义要公开的所有函数
......@@ -55,7 +55,7 @@ Initialization Segment
1. 调用`PyMODINIT_FUNC`宏。 此宏在 Python 标头中定义,并且在开始定义模块之前总是会被调用。
2. 下一行定义了初始化函数,并在加载该函数时由 Python 解释器调用。 函数名称必须为`init<module_name>`格式,C 代码将要公开的模块和函数的名称。
该函数的主体包含对`Py_InitModule3`的调用,该调用定义模块的名称和模块中的功能。 该函数的一般结构如下:
该函数的主体包含对`Py_InitModule3`的调用,该调用定义模块的名称和模块中的函数。 该函数的一般结构如下:
```c
(void)Py_InitModule3(name_of_module, method_array, Docstring)
......@@ -152,7 +152,7 @@ return Py_BuildValue("f", answer);
# 使用 NumPy C-API 创建数组平方函数
在本节中,我们将创建一个函数以对 NumPy 数组的所有值求平方。 这里的目的是演示如何在 C 语言中获取 NumPy 数组,然后对其进行迭代。 在现实世界中,可以使用地图或通过向量化平方函数以更简单的方式完成此操作。 我们正在使用与`O!`格式字符串相同的`PyArg_ParseTuple`函数。 该格式字符串具有`(object) [typeobject, PyObject *]`签名,并以 Python 类型对象作为第一个参数。 用户应阅读官方 API 文档,以查看允许使用其他格式的字符串以及哪种字符串适合他们的需求:
在本节中,我们将创建一个函数以对 NumPy 数组的所有值求平方。 这里的目的是演示如何在 C 语言中获取 NumPy 数组,然后对其进行迭代。 在现实世界中,可以使用映射或通过向量化平方函数以更简单的方式完成此操作。 我们正在使用与`O!`格式字符串相同的`PyArg_ParseTuple`函数。 该格式字符串具有`(object) [typeobject, PyObject *]`签名,并以 Python 类型对象作为第一个参数。 用户应阅读官方 API 文档,以查看允许使用其他格式的字符串以及哪种字符串适合他们的需求:
### 注意
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册