使用函数进行模块化编程

Beginner

This tutorial is from open-source community. Access the source code

简介

本节介绍模块的概念以及如何使用跨多个文件的函数。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 初级 级别的实验,完成率为 82%。获得了学习者 100% 的好评率。

模块与导入

任何 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 模块。它只是在完成后将 cossin 名称从模块复制到本地空间。

模块加载

每个模块仅加载和执行一次注意:重复导入只会返回对先前加载模块的引用。

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 中练习更多实验来提升你的技能。