---
jupyter:
jupytext:
text_representation:
extension: .md
format_name: markdown
format_version: '1.2'
jupytext_version: 1.3.0rc2
kernelspec:
display_name: Python 3
language: python
name: python3
---
.. meta::
:description: Topic: Understanding imports modules and packages, Difficulty: Easy, Category: Section
:keywords: custom package, module, script, import, setup, pip, conda, relative import, absolute import
# 导入:模组和包
在之前学习Python和NumPy基础时,我们需要经常使用 `import` 语句。这允许我们访问标准库和NumPy提供的函数和对象。
```python
# 访问标准库中 `collections` 模组的 `defaultdict`
from collections import defaultdict
# 导入整个numpy包并将给其代名词“np”
import numpy as np
```
尽管我们经常食用 `import` 语句,我们到现在为止都还没有讨论它具体的运作细节。在这里,我们将给予其足够的注意力并讨论Python的导入系统,也就是代码被组织成模组(module)和包(package)的方法。模组是单独的 `.py` 文件,而我们可以从模组中导入函数和对象;包则是这些模组的集合。具体讨论这个包系统不仅仅将帮助我们理解标准库和其它Python代码集的组织方法,这也将允许我们创建我们自己的代码包。
作为本节的总结,我们将演示在系统安装一个Python包的过程;假设你编写了你自己的Python包,安装该包将允许你从系统的任何地方导入它。我们将会简短地讨论两个最常用的向全世界提供Python包储存和下载功能的服务:Python Package Index(PyPI)和 Anaconda.org。
[官方Python教程](https://docs.python.org/3/tutorial/modules.html)提供了很棒的有关本节材料的总结以及在这里没有讨论的细节。
**自动重载**:
当你在Jupyter记事本中跟随本节的操作时,请将以下代码包含在记事本的顶端:
```python
%load_ext autoreload
%autoreload 2
```
执行这些“魔法命令”将会告知你的记事本在任何导入的模组和包被修改后重新导入它们。如果你不执行这些命令,你的记事本将不会“看到”你对已经导入的模组进行的修改,除非你重新启动该记事本的壳。
## 模组
Python“模组”指包含函数定义和变量赋值语句的单个 `.py` 文件。导入一个模组将执行这些命令并通过被导入的模组使得命令产生的对象可以被访问。
让我们创建属于自己的模组并将其导入到一个互动式的Python进程中吧。在你计算机中的某个路径中打开一个Jupyter记事本或IPython命令行。使用[IDE](http://cn.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html#Integrated-Development-Environments)或简单的文本编辑器(不要用像Microsoft Word这样的软件!)在进程的路径中创建一个名为 `my_module.py` 的文本文件。`my_module.py` 的内容应为:
```python
"""
我们的第一个Python模组。这个开头的字符串是模组级别的说明文档字符串(docstring)。
它并不是该模组必要的一部分,但它对描述你的模组的目的很有用。
"""
print("I am being executed!")
some_list = ["a", 1, None]
def square(x):
return x ** 2
def cube(x):
return x ** 3
```
回到我们的互动式Python进程,我们可以将这个模组导入到该进程中。因为它就在当前的路径中,Python能够直接“找到”这个模组——我们会在之后对此多做讨论。导入 `my_module.py` 将会从上到下执行它所有的代码,并会创建一个名为 `my_module` 的Python对象;这是内置的 `module` 类型的一个实例。请注意,我们在导入语句中没有包含 `.py` 后缀。
```python
# 将 my_module 导入到我们的互动进程中
>>> import my_module
I am being executed!
# 创建了一个模组类的实例对象
>>> my_module
>>> type(my_module)
module
```
正如所料,导入我们的模组导致 `print` 语句被执行,因此 `'I am being executed!'` 被打印到了命令行中。然后,对象 `some_list`,`square`,和 `cube` 在剩余的代码被执行时也被定义了。*我们可以通过模组对象来访问它们*。
```python
# 我们可以通过模组对象访问模组中定义的所有变量
>>> my_module.some_list
['a', 1, None]
>>> my_module.square
>>> my_module.square(2)
4
>>> my_module.cube(2)
8
```
你一定要理解这就是模组内容能够被导入它的环境访问的方法。知道这点后,了解模组的成员的一个好办法是使用IDE和互动命令行的[自动完成](http://cn.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Informal_Intro_Python.html#Dabbling-with-Numbers)功能来列出模组对象的所有属性。内置的 `help` 函数可以用来总结模组的内容:
```python
>>> help(my_module)
Help on module my_module:
NAME
my_module
DESCRIPTION
我们的第一个Python模组。这个开头的字符串是模组级别的说明文档字符串(docstring)。
它并不是该模组必要的一部分,但它对描述你的模组的目的很有用。
FUNCTIONS
cube(x)
square(x)
DATA
some_list = ['a', 1, None]
FILE
c:\users\ryan soklaski\desktop\learning_python\python\module5_oddsandends\my_module.py
```
**经验**:
模组仅仅是一个包含.py后缀的文本文件,其内容为Python代码。你可以在互动命令行环境(如Jupyter记事本)或另外一个模组中导入模组。导入模组会执行模组的代码并创建一个模组类型的对象实例。任何在导入时赋值的变量都被绑定到这个模组对象上。
**阅读理解:创建简单模组**
创建一个名为 `basic_math.py` 的简单数学模组。它应该提供无理数 $\pi$ 和 $e$,以及函数 `deg_to_rad`,其将角度数转化成弧度数。
然后,导入这个模组,计算 $e^{i\pi}$,并将45度转化为对应的弧度。
## 导入语句
Python提供了导入模组和具体模组成员的灵活框架。我们已经在关于NumPy的讨论中见到了我们可以在导入语句提供一个*代名词*(alias)这一点;这在导入长名字的模组时非常方便:
```python
# NumPy的模组对象由名为 `np` 的变量代表
>>> import numpy as np
>>> np.array([2., 3.])
array([2., 3.])
```
一般而言,我们可以如此为导入的模组设置代名词:`import as `。
然后,语法 `from import , , ...` 允许我们从模组导入具体的对象,而不是整个模组。让我们从 `basic_module.py` 导入 `square` 和 `some_list`:
```python
>>> from my_module import square, some_list
I am being executed!
>>> some_list
['a', 1, None]
>>> square(2)
4
```
请注意,模组依然整个被执行,但是与其创建模组实例 `my_module`,这个导入语句仅仅返回了模组中定义的对象中在我们导入语句中提供名字的那些。
最后,你可以提供 `*` 来代表模组的所有属性。
```python
# 导入 `my_module` 的所有成员
>>> from my_module import *
```
你可以在模组中提供一个名为 `__all__` 的列表,其将属性名储存为字符串,来限制 `*` 代表的属性。也就是说,如果我们在 `my_module.py` 中包含了 `__all__ = ["cube", "some_list"]`,那么 `from my_module import *` 将只会导入 `cube` 和 `some_list`,而不会导入 `square`。
最后,我们也可以在这种风格的导入中使用代名词:
```python
>>> from my_module import cube as my_cube
>>> my_cube(2)
8
```
## 包
在大项目中工作时,你经常会想要将你的代码组织成多个模组。比如说,假设我们在编写脸部识别的软件。我们可以会需要一个摄像机模组来获取照片,一个脸部识别模组来储存能够识别人脸的类,以及一个数据库模组来储存和更新“见过的”脸。这些模组可以在一个共同的*包*(package)中。
Python包是一个包含名为 `__init__.py` 的文件的路径,以及其它Python模组和子包(也就是说,有着属于自己的 `__init__.py` 文件和有关模组的子路径)。`__init__.py` 文件有着特殊的重要性——它用来标示其所在的路径应作为包处理。作为一个例子,让我们创建一个最基础的包,其文件结构如下:
```
- 你当前的Jupyter记事本进程/命令行进程
- a_dir/
|--__init__.py
```
请注意,你的互动Python进程(如Jupyter记事本)应该在和 `a_dir/` 相同的路径下活跃。包含 `__init__.py` 文件的路径名字就是包的名字,因此这个包的名字为 `a_dir`。假设 `__init__.py` 的内容如下:
```python
def sum_func(x, y):
return x + y
def divide_func(x, y):
return x / y
```
和模组一样,导入这个包会执行 `__init__.py` 的内容并将 `sum_func` 和 `divide_func` 提供为创建的模组对象的属性:
```python
# 导入Python包
>>> import a_dir
>>> a_dir.divide_func(1, 2)
0.5
```
让我们讨论一个更加成熟的包,其包含模组和子包。下面的包,`face_detection`,包含了模组 `utils`,`database`,和 `model`。它也包含了子包 `camera`,其包含了 `config` 模组和 `calibration` 模组。
```
- face_detection/
|-- __init__.py
|-- utils.py
|-- database.py
|-- model.py
|-- camera/
|-- __init__.py
|-- calibration.py
|-- config.py
```
我们可以通过 `.`,`..`,和类似的语法来访问这些模组的内容。如以下范例:
```python
# 从 `database` 模组导入一个函数
>>> from face_detection.database import load_database
# 导入整个 `model` 模组
>>> from face_detection import model
# 从 `camera` 子包导入一个函数
>>> from face_detection.camera.config import restore_default
```
请注意,`.` 语法允许我们相对顶层包一层层深入其中的模组和子包。
**阅读理解:包**
假设我们在使用一个名为 `mail` 的包。路径 `mail/` 包含了一个 `__init__.py` 模组,其内容为:
```python
def send_mail(x):
return x
phrase_of_the_day = "get that package delievered!"
```
它也包含了一个模组 `delivery.py`,其内容为:
```python
def get_zip():
"""just a dummy function"""
return 871092
```
1. 创建这个包。
2. 导入 `send_mail` 函数。
3. 导入 `delivery` 模组并执行它的 `get_zip` 函数
## 模组内导入
包中的模组可以互相导入;比如说,假设 `face_detection.database` 和 `face_detection.camera.calibration` 都想要使用 `face_detection.utils` 模组。请回忆本包的结构:
```
- face_detection/
|-- __init__.py
|-- utils.py
|-- database.py
|-- model.py
|-- camera/
|-- __init__.py
|-- calibration.py
|-- config.py
```
我们有两种导入风格来进行模组之间的导入:绝对导入和相对导入。
### 绝对导入
绝对导入风格允许你通过提供模组相对顶层包的绝对位置来选择模组。假设我们想要在 `model` 模组中通过绝对导入来导入 `utils` 模组,那么代码将是:
```python
import face_detection.utils
```
或,使用代名词:
```python
import face_detection.utils as utils
```
假设我们也想要在 `face_detection.camera.calibration` 中导入 `utils` 模组;绝对导入的语句和之前一样,因为 `utils` 相对顶层包的相对位置不会根据我们导入它的路径改变。
作为一个额外的例子,在包中任何位置导入 `camera` 的 `config` 模组的绝对导入语句如下:
```python
import face_detection.camera.config
```
绝对导入语法支持我们上方枚举的所有变化,如代名词和 `from import