4-2-模块代码编写基础
1. 模块的创建
定义模块,只要使用文本编辑器,把一些 Python 代码输入至文本文件中,然后以“.py”为后缀名进行保存,任何此类文件都会被自动认为是 Python 模块。在模块顶层指定的所有变量名都会变成其属性,并且可以导出供客户端来使用。
例如,如果在名为 module1.py 的文件中输入下面的 def 语句,并将这个文件导入,就会创建一个拥有一个属性的模块对象。
1 | def printer(x): |
1.1 模块文件名
模块名在 Python 程序中会变成变量名(没有.py)。当一个模块被导入时,Python 会把内部模块名映射到外部文件名,也就是说通过把模块搜索路径中的目录路径加在前边,而 .py 或其他后缀名添加在后边。
2. 模块的使用
客户端可以执行 import 或 from 语句。import 会读取整个模块,所以必须进行定义后才能读取它的变量名;from 将获取(或者说复制)模块特定的变量名。
2.1 import 语句
上面的变量名 module1 有两个不同的目的:识别要被载入的外部文件,同时会生成脚本中的变量,在文件加载后,用来引用模块对象。
1 | import module1 |
Hello world!
import 语句使用逗号分隔,可以载入多个模块。因为 import 使一个变量名引用整个模块对象,我们必须通过模块名称来得到该模块的属性(例如,module1.printer)。
2.2 from 语句
因为 from 会把变量名复制到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不需要通过模块。
1 | from module1 import printer |
Hello world!
2.3 from * 语句
当我们使用 * 时,会取得模块顶层所有赋了值的变量名的拷贝。还是在脚本中使用复制后得到的变量名 printer,而不需要通过模块名。
1 | from module1 import * |
Hello world!
2.4 导入只发生一次
模块会在第一次 import 或 from 时载入并执行,并且只在第一次如此。之后的导入操作都只会取出已加载的模块对象。
初始化代码
1 | import simple # 第一次导入 |
hello
1
第二次和其后的导入并不会重新执行此模块的代码,只是从 Python 内部模块表中取出已创建的模块对象。
1 | simple.spam = 2 |
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 | import 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 | import module # 初始化 import |
一般的用法是:导入一个模块,在文本编辑器内修改其原代码,然后将其重载。当调用 reload 时,Python 会重读模块文件的源代码,重新执行其顶层语句。
- **reload 会在模块当前命名空间内执行模块文件的新代码。**重新执行模块文件的代码会覆盖其现有的命名空间,并进行删除而进行重建。
- 文件中顶层赋值语句会使得变量名换成新值。
- 重载会影响所有使用 import 读取了模块的客户端。
- 重载只会对以后使用 from 的客户端造成影响。
- 重载只应用于单个模块。
4.2 reload 实例
下面,我们要修改并重载一个模块文件,但不会中止交互模式的 Python 会话。
首先,在文本编辑器中,编写一个名为 changer.py 的模块文件,其内容如下:
1 | message = 'First version' |
启动 Python 解释器,导入该模块,然后调用其导出的函数:
1 | import changer |
First version
不要关掉解释器,在另一个窗口中编辑该模块文件,改变 message 变量和 printer 函数体:
1 | message = 'After editing' |
1 | import changer |
First version
回到 Python 窗口,重载该模块从而获得新的代码:
1 | from imp import reload |
<module 'changer' from 'C:\\Users\\lan\\Desktop\\Python\\Python-note\\Learning Python\\五、模块和包\\changer.py'>
1 | changer.printer() |
reloaded: After editing