简介
本节介绍模块的概念以及如何使用跨多个文件的函数。
模块与导入
任何 Python 源文件都是一个模块。
## foo.py
def grok(a):
...
def spam(b):
...
import 语句会加载并执行一个模块。
## program.py
import foo
a = foo.grok(2)
b = foo.spam('Hello')
...
命名空间
一个模块是一组命名值的集合,有时也被称为一个命名空间。这些名称是源文件中定义的所有全局变量和函数。导入之后,模块名用作前缀。因此就有了命名空间。
import foo
a = foo.grok(2)
b = foo.spam('Hello')
...
模块名直接与文件名相关联(foo -> foo.py)。
全局定义
在全局作用域中定义的所有内容都会填充到模块命名空间中。假设有两个模块都定义了相同的变量 x。
## foo.py
x = 42
def grok(a):
...
## bar.py
x = 37
def spam(a):
...
在这种情况下,x 的定义引用了不同的变量。一个是 foo.x,另一个是 bar.x。不同的模块可以使用相同的名称,并且这些名称不会相互冲突。
模块是相互隔离的。
作为环境的模块
模块为其内部定义的所有代码形成一个封闭环境。
## foo.py
x = 42
def grok(a):
print(x)
全局变量始终绑定到封闭模块(同一个文件)。每个源文件都是它自己的小世界。
模块执行
当一个模块被导入时,模块中的所有语句会依次执行,直到到达文件末尾。模块命名空间的内容是在执行过程结束时仍然定义的所有全局名称。如果有在全局作用域中执行任务的脚本语句(打印、创建文件等),你会在导入时看到它们运行。
import as 语句
你可以在导入模块时更改其名称:
import math as m
def rectangular(r, theta):
x = r * m.cos(theta)
y = r * m.sin(theta)
return x, y
它的工作方式与普通导入相同。只是在该文件中重命名了模块。
从模块中导入
这会从模块中挑选出选定的符号,并使它们在本地可用。
from math import sin, cos
def rectangular(r, theta):
x = r * cos(theta)
y = r * sin(theta)
return x, y
这样可以在不输入模块前缀的情况下使用模块的部分内容。这对于常用名称很有用。
关于导入的说明
导入方式的变化不会改变模块的工作方式。
import math
## 与
import math as m
## 与
from math import cos, sin
...
具体来说,import 总是会执行整个文件,并且模块仍然是隔离的环境。
import module as 语句只是在本地更改名称。from math import cos, sin 语句在幕后仍然会加载整个 math 模块。它只是在完成后将 cos 和 sin 名称从模块复制到本地空间。
模块加载
每个模块仅加载和执行一次。注意:重复导入只会返回对先前加载模块的引用。
sys.modules 是所有已加载模块的字典。
>>> import sys
>>> sys.modules.keys()
['copy_reg', '__main__','site', '__builtin__', 'encodings', 'encodings.encodings', 'posixpath',...]
>>>
注意:如果你在更改模块的源代码后重复 import 语句,可能会产生常见的混淆。由于模块缓存 sys.modules,重复导入总是返回先前加载的模块 —— 即使进行了更改。将修改后的代码加载到 Python 中的最安全方法是退出并重新启动解释器。
定位模块
Python 在查找模块时会参考一个路径列表(sys.path)。
>>> import sys
>>> sys.path
[
'',
'/usr/local/lib/python36/python36.zip',
'/usr/local/lib/python36',
...
]
当前工作目录通常排在首位。
模块搜索路径
如前所述,sys.path 包含搜索路径。如有需要,你可以手动调整。
import sys
sys.path.append('/project/foo/pyfiles')
路径也可以通过环境变量添加。
% env PYTHONPATH=/project/foo/pyfiles python3
Python 3.6.0 (default, Feb 3 2017, 05:53:21)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]
>>> import sys
>>> sys.path
['','/project/foo/pyfiles',...]
一般来说,无需手动调整模块搜索路径。不过,当你尝试导入位于不寻常位置或当前工作目录无法轻易访问的 Python 代码时,有时就需要这么做。
对于这个涉及模块的练习,确保你在合适的环境中运行 Python 至关重要。模块常常会给新程序员带来与当前工作目录或 Python 路径设置相关的问题。对于本课程,假定你将所有代码都写在 ~/project 目录中。为了获得最佳效果,启动解释器时你也应该确保处于该目录中。如果不是,你需要确保将 ~/project 添加到 sys.path 中。
练习 3.11:模块导入
在第 3 节中,我们创建了一个通用函数 parse_csv() 来解析 CSV 数据文件的内容。
现在,我们将看看如何在其他程序中使用该函数。首先,在一个新的 shell 窗口中开始。导航到你存放所有文件的文件夹。我们将导入这些文件。
启动 Python 交互模式。
$ python3
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
完成上述操作后,尝试导入一些你之前编写的程序。你应该会看到和之前完全一样的输出。需要强调的是,导入一个模块会运行其代码。
>>> import bounce
... 查看输出...
>>> import mortgage
... 查看输出...
>>> import report
... 查看输出...
>>>
如果这些都不起作用,你可能在错误的目录中运行 Python。现在,尝试导入你的 fileparse 模块并获取相关帮助。
>>> import fileparse
>>> help(fileparse)
... 查看输出...
>>> dir(fileparse)
... 查看输出...
>>>
尝试使用该模块读取一些数据:
>>> portfolio = fileparse.parse_csv('/home/labex/project/portfolio.csv',select=['name','shares','price'], types=[str,int,float])
>>> portfolio
... 查看输出...
>>> pricelist = fileparse.parse_csv('/home/labex/project/prices.csv',types=[str,float], has_headers=False)
>>> pricelist
... 查看输出...
>>> prices = dict(pricelist)
>>> prices
... 查看输出...
>>> prices['IBM']
106.28
>>>
尝试导入一个函数,这样你就不需要包含模块名:
>>> from fileparse import parse_csv
>>> portfolio = parse_csv('/home/labex/project/portfolio.csv', select=['name','shares','price'], types=[str,int,float])
>>> portfolio
... 查看输出...
>>>
练习 3.12:使用你的库模块
在第 2 节中,你编写了一个名为 report.py 的程序,它生成了一份类似这样的股票报告:
名称 持有量 价格 变化
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
获取该程序并进行修改,以便所有输入文件处理都使用 fileparse 模块中的函数来完成。为此,将 fileparse 作为模块导入,并将 read_portfolio() 和 read_prices() 函数修改为使用 parse_csv() 函数。
以本练习开头的交互式示例为指导。之后,你应该会得到与之前完全相同的输出。
练习 3.13:故意留白(跳过)
练习 3.14:使用更多库导入
在第 1 节中,你编写了一个名为 pcost.py 的程序,它读取一个投资组合并计算其成本。
>>> import pcost
>>> pcost.portfolio_cost('/home/labex/project/portfolio.csv')
44671.15
>>>
修改 pcost.py 文件,使其使用 report.read_portfolio() 函数。
注释
完成本练习后,你应该有三个程序。fileparse.py,它包含一个通用的 parse_csv() 函数。report.py,它生成一份不错的报告,还包含 read_portfolio() 和 read_prices() 函数。最后是 pcost.py,它计算投资组合成本,但使用了为 report.py 程序编写的 read_portfolio() 函数。
总结
恭喜!你已完成实验模块。你可以在 LabEx 中练习更多实验来提升你的技能。