4-2-模块代码编写基础

1. 模块的创建

定义模块,只要使用文本编辑器,把一些 Python 代码输入至文本文件中,然后以“.py”为后缀名进行保存,任何此类文件都会被自动认为是 Python 模块。在模块顶层指定的所有变量名都会变成其属性,并且可以导出供客户端来使用。

例如,如果在名为 module1.py 的文件中输入下面的 def 语句,并将这个文件导入,就会创建一个拥有一个属性的模块对象。

1
2
def printer(x):
print(x)

1.1 模块文件名

模块名在 Python 程序中会变成变量名(没有.py)。当一个模块被导入时,Python 会把内部模块名映射到外部文件名,也就是说通过把模块搜索路径中的目录路径加在前边,而 .py 或其他后缀名添加在后边。

2. 模块的使用

客户端可以执行 import 或 from 语句。import 会读取整个模块,所以必须进行定义后才能读取它的变量名;from 将获取(或者说复制)模块特定的变量名。

2.1 import 语句

上面的变量名 module1 有两个不同的目的:识别要被载入的外部文件,同时会生成脚本中的变量,在文件加载后,用来引用模块对象。

1
2
import module1
module1.printer('Hello world!')
Hello world!

import 语句使用逗号分隔,可以载入多个模块。因为 import 使一个变量名引用整个模块对象,我们必须通过模块名称来得到该模块的属性(例如,module1.printer)。

2.2 from 语句

因为 from 会把变量名复制到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不需要通过模块。

1
2
from module1 import printer
printer('Hello world!')
Hello world!

2.3 from * 语句

当我们使用 * 时,会取得模块顶层所有赋了值的变量名的拷贝。还是在脚本中使用复制后得到的变量名 printer,而不需要通过模块名。

1
2
from module1 import *
printer('Hello world!')
Hello world!

2.4 导入只发生一次

模块会在第一次 import 或 from 时载入并执行,并且只在第一次如此。之后的导入操作都只会取出已加载的模块对象。

初始化代码

1
2
import simple                 # 第一次导入
simple.spam
hello

1

第二次和其后的导入并不会重新执行此模块的代码,只是从 Python 内部模块表中取出已创建的模块对象。

1
2
3
simple.spam = 2
import simple # 只是取出已创建的模块
simple.spam # 属性没有改变
2

2.5 import 和 from 是赋值语句

import 和 from 是可执行的语句,可以嵌套在 if 测试中,出现在函数 def、try 语句之中等。

import 和 from 都是隐形的赋值语句:

  • import 将整个模块对象赋值给一个变量名。
  • from 将一个或多个变量名赋值给另一个模块中同名的对象。

2.6 import 和 from 的对等性

一个像这样的 from 语句:

1
from module import name1, name2

与下面这些语句是等效的:

1
2
3
4
import module
name1 = module.name1
name2 = module.name2
del module

3. 模块命名空间

3.1 文件生成命名空间

  • 模块语句会在首次导入时执行。
  • 顶层的赋值语句会创建模块属性。
  • 模块的命名空间能通过属性 __dict__ 或 dir(M) 获取。
  • 模块是一个独立的作用域(本地变量就是全局变量)。

3.2 命名空间字典:__dict__

在内部模块命名空间是作为字典对象进行存储的。可以通过模块的 __dict__ 属性获取模块命名空间字典:

1
list(module1.__dict__.keys())
['__name__',
 '__doc__',
 '__package__',
 '__loader__',
 '__spec__',
 '__file__',
 '__cached__',
 '__builtins__',
 'printer']

3.3 属性名的点号运算

在 Python 中,可以使用点号运算语法 object.attribute 获取任意 object 的 attribute 属性。

属性的点号运算与作用域法则无关,LEGB 规则只适用于无点号运算的纯变量名。以下是其规则:

  • **简单变量。**X 是指在当前作用域内搜索变量名 X(遵循 LEGB 规则)。
  • **点号运算。**X.Y 是指在当前范围内搜索 X,然后搜索对象 X 之中的属性 Y(而非在作用域内)。
  • **多层点号运算。**X.Y.Z 指的是寻找对象 X 之中的变量名 Y,然后再找对象 X.Y 之中的 Z。
  • **通用性。**点号运算可用于任何具有属性的对象:模块、类、C 扩展类型等。

3.4 导入和作用域

导入操作不会赋予被导入文件中的代码对上层代码的可见度:被导入文件无法看见进行导入的文件内的变量名。更确切的说法是:

  • 函数绝对无法看见其他函数内的变量名,除非它们从物理上处于这个函数内。
  • 模块程序代码绝对无法看见其他模块内的变量名,除非明确地进行了导入。

在 Python 中,一段程序的作用域完全由程序所处的文件中实际位置决定。作用域绝不会被函数调用或模块导入影响。

4. 重载模块

调用 reload 内置函数可以强制使模块代码重新载入并重新运行。此文件中新的代码的赋值语句会在适当的地方修改现有的模块对象。

reload 函数可以修改程序的一些部分,而无需停止整个程序。因此,利用 reload,可以立即看到对组件的修改的效果。

在 Python 3.X 中,reload 在 imp 标准库模块中,需要 import 语句或 from 语句来载入该工具。

4.1 reload 基础

  • reload 是 Python 中的内置函数,而不是语句。
  • 传给 reload 的是已经存在的模块对象,而不是变量名。
  • reload 在 Python 3.X 中位于模块中,并且必须导入自己。

在重载之前,模块一定是已经预先成功导入了。此外,import 语句和 reload 调用的语法并不相同:reload 需要小括号,但 import 不需要。

1
2
3
4
5
6
7
import module              # 初始化 import
...use module.attributes...
... # 现在,改变 module 文件
...
from imp import reload # 获取 reload
reload(module) # 获取更新
...use module.attributes...

一般的用法是:导入一个模块,在文本编辑器内修改其原代码,然后将其重载。当调用 reload 时,Python 会重读模块文件的源代码,重新执行其顶层语句。

  • **reload 会在模块当前命名空间内执行模块文件的新代码。**重新执行模块文件的代码会覆盖其现有的命名空间,并进行删除而进行重建。
  • 文件中顶层赋值语句会使得变量名换成新值。
  • 重载会影响所有使用 import 读取了模块的客户端。
  • 重载只会对以后使用 from 的客户端造成影响。
  • 重载只应用于单个模块。

4.2 reload 实例

下面,我们要修改并重载一个模块文件,但不会中止交互模式的 Python 会话。

首先,在文本编辑器中,编写一个名为 changer.py 的模块文件,其内容如下:

1
2
3
message = 'First version'
def printer():
print(message)

启动 Python 解释器,导入该模块,然后调用其导出的函数:

1
2
import changer
changer.printer()
First version

不要关掉解释器,在另一个窗口中编辑该模块文件,改变 message 变量和 printer 函数体:

1
2
3
message = 'After editing'
def printer():
print('reloaded:', message)
1
2
import changer
changer.printer() # 再次导入模块没有效果,得到原始的 message
First version

回到 Python 窗口,重载该模块从而获得新的代码:

1
2
from imp import reload
reload(changer) # 强制载入运行新代码
<module 'changer' from 'C:\\Users\\lan\\Desktop\\Python\\Python-note\\Learning Python\\五、模块和包\\changer.py'>
1
changer.printer()
reloaded: After editing