# 模块与包
模块(Module)
- py 脚本文件可以被 Python 解释器直接运行,也可以被其它脚本文件通过 import 语句导入其成员,此时称为模块。
包(Package)
- 在一个目录中加入一个名为
__init__.py
的文件(内容可以为空),Python 解释器就会将该目录识别为包。 - 使用包便于管理同一目录下的多个模块。
- 包目录支持嵌套。
- 在一个目录中加入一个名为
库(Library)
- 可以将能独立实现某些功能的 Python 代码封装成一个库,发布给其他用户使用。
- 简单的库可能只包含一个模块,复杂的库可能包含几十个包。
# import
在脚本中,用
import
关键字可以导入某个模块或包:import sys # 导入 sys 模块 print('path :', sys.path) # 调用 sys 模块的成员
- 用
import xx
语句导入一个包时,相当于执行import xx.__init__
。
- 用
用
from xx import xx
语句可以导入某个模块或包的成员,加入当前作用域:from sys import argv, path print('path :', path) # 不需要用 sys. 开头,就可以直接访问导入的成员。不过这样可能与当前作用域的标识符重名
from sys import * # 用 * 可以导入全部成员。不过这样可读性差,如果不需要导入很多成员,则不建议这样做
用
as
关键字可以将导入的标识符重命名:import sys as _sys from sys import path as _path
# 寻址
导入某个模块或包时,Python 解释器会在 sys 模块的 path 目录列表中寻找该文件并导入。
- 如果没找到则报错:
ModuleNotFoundError: No module named 'xx'
- 用户可以主动在 sys.path 中增加搜索路径,不过每次重启 Python 解释器时都会重置 sys.path 的值。
- 如果没找到则报错:
用 importlib 模块的 import_module() 函数可以根据 str 类型的模块名导入模块:
>>> from importlib import import_module >>> m = import_module('os') # 相当于 import os >>> m <module 'os' from '/usr/lib64/python3.6/os.py'> >>> m = import_module('os.path') # 相当于 import os.path >>> m <module 'posixpath' from '/usr/lib64/python3.6/posixpath.py'> >>> m = import_module('sys', package='os') # 相当于 from os import sys >>> m <module 'sys' (built-in)>
# 局部导入
- 局部导入:如果在函数或方法内加入 import 语句,则执行该语句块时才会执行 import 语句,并且导入的模块只能在该局部作用域内被访问。
- pyinstaller 在打包 Python 脚本时会收集局部导入的模块,即使该 import 语句不会被执行。
# 间接导入
导入一个模块时,实际上是先执行它的全部内容(像正常执行脚本一样),然后将它创建的所有对象保留到该模块的作用域中,供当前脚本访问。
- 例如:如果用 Python 解释器执行 1.py ,而 1.py 导入了 2.py ,则最终两个脚本都会被执行一次,只不过一个是被直接执行,一个是被间接执行。
- 例如,编写一个 test.py :
print('Hello') def fun1(): print('fun1') def fun2(): print('fun2') fun1()
- 然后在终端导入该脚本:
>>> from test import fun2 Hello # 执行 print('Hello') >>> fun2() fun2 fun1 # 导入一个函数或类时,其调用的其它内容会被自动导入 >>> fun1() # 但是并不能被直接访问 NameError: name 'fun1' is not defined >>> from test import fun1 # 多次导入一个模块时,只会执行一次作为初始化,不会重复执行它,因此这里不会再执行 print('Hello') >>> fun1() fun1
在包内的脚本中,可以用相对地址进行导入:
from .utils import * # 从当前目录导入(需要当前目录是一个包) from .. import * # 从上层目录导入(需要当前目录、上层目录都是包)
- 如果该脚本被间接执行,则 Python 解释器会自动将这些相对地址转换成绝对地址。
- 如果该脚本被直接执行,则 Python 解释器不能处理相对地址,会报错。
# 循环导入
- 循环导入:两个模块相互导入,或者多个模块的导入关系构成一个回路。
- 此时相当于将多个模块的可执行语句,按导入顺序组合成一个大脚本,如果尝试访问尚未初始化的成员,就会报错。
- 例:
创建两个循环导入的脚本文件:
A.py :print('start', __name__) import B id = 1 print(B.id) print('end', __name__)
B.py :
print('start', __name__) import A id = 2 print(A.id) print('end', __name__)
执行时输出如下:
[[email protected] ~]# python3 A.py start __main__ start B start A # 此时 B.py 导入 A.py ,从头执行其中的代码,重复执行了 print('start', __name__) Traceback (most recent call last): File "A.py", line 3, in <module> # 此时 A.py 尚未完成初始化,就导入 B.py ,输出 start B import B File "B.py", line 3, in <module> # 此时 B.py 尚未完成初始化,就导入 A.py ,输出 start A import A File "A.py", line 5, in <module> # 此时在 A.py 中访问 B.id ,但该变量尚未创建,所以报错 print(B.id) AttributeError: partially initialized module 'B' has no attribute 'id' (most likely due to a circular import)
将 A.py 中的
print(B.id)
改为print(dir(B))
,就可以循环导入。如下:[[email protected] ~]# python3 A.py start __main__ start B start A ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] end A # 此时 B.py 尚未完成初始化,但导入的 A.py 已经完整执行了一次,完成了初始化 1 # 此时可以在 B.py 中访问 A.id end B ['A', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'id'] end __main__
也可以将 A.py 中的 print(B.id) 放到不会立即执行的代码块中:
def test(): print(B.id)
# 脚本的内置属性
__file__
:脚本的文件路径,由 Python 解释器自动赋值。- 当脚本被直接运行时,其值为执行该脚本时使用的文件路径(可能是绝对路径、相对路径);当脚本被间接运行时,其值为该脚本的绝对路径。
__name__
:脚本的名称,由 Python 解释器自动赋值。- 当脚本被直接运行时,其值为
"__main__"
;当脚本被间接运行时,其值为该脚本的模块名,即将脚本的文件名去掉 .py 后缀。 - 例如:下方语句块只会在脚本被直接运行时执行,当脚本被间接运行时不会执行。
if __name__ == '__main__': try: main() except: raise
- 例如,编写一个 test.py :
print(__file__) print(__name__)
- 在终端执行该脚本时,其输出为:
[[email protected] ~]# python test.py test.py __main__ [[email protected] ~]# python /root/test.py /root/test.py __main__
- 在 Python 解释器中导入该脚本时,其输出为:
>>> import test /root/test.py test
- 当脚本被直接运行时,其值为
__all__
:一个列表,决定了用from xx import *
导入该脚本时会导入哪些成员。- 如果用户没有定义该属性,则默认会导入该脚本内的所有公有成员(即标识符不以下划线
_
开头)。 - 例:
from sys import path __all__ = ['path', 'fun1'] def fun1(): pass
- 如果用户没有定义该属性,则默认会导入该脚本内的所有公有成员(即标识符不以下划线