模块
脚本
如果你从Python解释器退出并再次进入,之前的定义(函数和变量)都会丢失。
因此,如果你想编写一个稍长些的程序,最好使用文本编辑器为解释器准备输入并将该文件作为输入运行。
这被称作编写脚本。
模块
随着程序变得越来越长,你或许会想把它拆分成几个文件,以方便维护。
你亦或想在不同的程序中使用一个便捷的函数,而不必把这个函数复制到每一个程序中去。
为支持这些,Python有一种方法可以把定义放在一个文件里,并在脚本或解释器的交互式实例中使用它们。
这样的文件被称作模块 ;模块中的定义可以导到其它模块或者主模块(你在顶级和计算器模式下执行的脚本中可以访问的变量集合)。
模块是一个包含Python定义和语句的文件。文件名就是模块名后跟文件后缀 .py
。
在一个模块内部,模块名(作为一个字符串)可以通过全局变量 __name__
的值获得。
入门例子
定义
例如,使用你最喜爱的文本编辑器在当前目录下创建一个名为 fibo.py 的文件, 文件中含有以下内容:
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
导入
然后命令行直接当前路径进入 python 命令解释器。
$ python
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
- 导入
使用如下命令即可导入我们定义的模块。
>>> import fibo
测试
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(1000)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
>>> fibo.__name__
'fibo'
python 模块导入机制
问题
上面 import 之后发生了什么?
为什么我们可以使用这个模块中的方法了?
流程
-
当一个名为 spam 的模块被导入的时候,解释器首先寻找具有该名称的内置模块。
-
如果没有找到,然后解释器从 sys.path 变量给出的目录列表里寻找名为 spam.py 的文件。
sys.path 初始有这些目录地址:
(1) 包含输入脚本的目录( 或者未指定文件时的当前目录)。
(2) PYTHONPATH (一个包含目录名称的列表,它和shell变量 PATH 有一样的语法)。
(3)取决于安装的默认设置
注解 在支持符号链接的文件系统上,包含输入脚本的目录是在追加符号链接后才计算出来的。
换句话说,包含符号链接的目录并 没有 被添加到模块的搜索路径上。
在初始化后,Python程序可以更改 sys.path。包含正在运行脚本的文件目录被放在搜索路径的开头处, 在标准库路径之前。
这意味着将加载此目录里的脚本,而不是标准库中的同名模块。
除非有意更换,否则这是错误。
解答
我们那个就是在(1)中。
更多的导入姿势
模块可以包含可执行的语句以及函数定义。这些语句用于初始化模块。它们仅在模块 第一次 在 import 语句中被导入时才执行。
(当文件被当作脚本运行时,它们也会执行。)
每个模块都有它自己的私有符号表,该表用作模块中定义的所有函数的全局符号表。
因此,模块的作者可以在模块内使用全局变量,而不必担心与用户的全局变量发生意外冲突。
另一方面,如果你知道自己在做什么,则可以用跟访问模块内的函数的同样标记方法,去访问一个模块的全局变量,modname.itemname
。
模块可以导入其它模块。习惯上但不要求把所有 import 语句放在模块(或脚本)的开头。被导入的模块名存放在调入模块的全局符号表中。
直接导入
import 语句有一个变体,它可以把名字从一个被调模块内直接导入到现模块的符号表里。
例如:
>>> from fibo import fib, fib2
>>> from fibo import fib, fib2
>>> fib(50)
0 1 1 2 3 5 8 13 21 34
这并不会把被调模块名引入到局部变量表里(因此在这个例子里,fibo 是未被定义的)。
导入模块下所有方法
还有一个变体甚至可以导入模块内定义的所有名称
>>> from fibo import *
>>> fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
这会调入所有非以下划线(_
)开头的名称。
在多数情况下,Python程序员都不会使用这个功能,因为它在解释器中引入了一组未知的名称,而它们很可能会覆盖一些你已经定义过的东西。
注意通常情况下从一个模块或者包内调入 *
的做法是不太被接受的, 因为这通常会导致代码的可读性很差。
不过,在交互式编译器中为了节省打字可以这么用。
as 别称
模块别名
如果模块名称之后带有 as,则跟在 as 之后的名称将直接绑定到所导入的模块。
>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这会和 import fibo 方式一样有效地调入模块, 唯一的区别是它以 fib 的名称存在的。
方法别名
或者你想给方法起一个别称
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
导入对于性能的考虑
出于效率的考虑,每个模块在每个解释器会话中只被导入一次。
因此,如果你更改了你的模块,则必须重新启动解释器,
或者,如果它只是一个要交互式地测试的模块,
请使用 importlib.reload()
,
例如
import importlib;
importlib.reload(modulename)
和 java 的对比
-
import 有些类似于 java 中的导包
-
java 中没有提供类似于 as 这种方便的用法。
模块的其他知识
以脚本的方式执行模块
当你用下面方式运行一个Python模块:
python fibo.py <arguments>
模块里的代码会被执行,就好像你导入了模块一样,但是 __name__
被赋值为 "__main__"
。
这意味着通过在你的模块末尾添加这些代码:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
你既可以把这个文件当作脚本又可当作一个可调入的模块来使用, 因为那段解析命令行的代码只有在当模块是以“main”文件的方式执行的时候才会运行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
如果模块是被导入的,那些代码是不运行的:
>>> import fibo
>>>
这经常用于为模块提供一个方便的用户接口,或用于测试(以脚本的方式运行模块从而执行一些测试套件)。
测试
关于测试,个人猜测肯定有类似于 junit 的单元测试框架,所以不是很建议采用这种方式。
“编译过的”Python文件
为了加速模块载入,Python在 __pycache__
目录里缓存了每个模块的编译后版本,名称为 module.version.pyc
,其中名称中的版本字段对编译文件的格式进行编码; 它一般使用Python版本号。
例如,在CPython版本3.3中,spam.py的编译版本将被缓存为 __pycache__/spam.cpython-33.pyc
。
此命名约定允许来自不同发行版和不同版本的Python的已编译模块共存。
Python根据编译版本检查源的修改日期,以查看它是否已过期并需要重新编译。这是一个完全自动化的过程。此外,编译的模块与平台无关,因此可以在具有不同体系结构的系统之间共享相同的库。
不检查缓存的情况
Python在两种情况下不会检查缓存。
首先,对于从命令行直接载入的模块,它从来都是重新编译并且不存储编译结果;
其次,如果没有源模块,它不会检查缓存。为了支持无源文件(仅编译)发行版本, 编译模块必须是在源目录下,并且绝对不能有源模块。
使用建议
给专业人士的一些小建议:
你可以在Python命令中使用 -O 或者 -OO 开关, 以减小编译后模块的大小。
-O 开关去除断言语句,-OO 开关同时去除断言语句和 __doc__
字符串。由于有些程序可能依赖于这些,你应当只在清楚自己在做什么时才使用这个选项。
“优化过的”模块有一个 opt- 标签并且通常小些。
将来的发行版本或许会更改优化的效果。
一个从 .pyc 文件读出的程序并不会比它从 .py 读出时运行的更快,.pyc 文件唯一快的地方在于载入速度。
compileall 模块可以为一个目录下的所有模块创建.pyc文件。
系统内置
标准模块
Python附带了一个标准模块库,在单独的文档Python库参考(以下称为“库参考”)中进行了描述。
一些模块内置于解释器中;它们提供对不属于语言核心但仍然内置的操作的访问,以提高效率或提供对系统调用等操作系统原语的访问。
这些模块的集合是一个配置选项,它也取决于底层平台。
例子
例如,winreg 模块只在Windows操作系统上提供。一个特别值得注意的模块 sys,它被内嵌到每一个Python解释器中。
变量 sys.ps1 和 sys.ps2 定义用作主要和辅助提示的字符串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
这两个变量只有在编译器是交互模式下才被定义。
sys.path
sys.path 变量是一个字符串列表,用于确定解释器的模块搜索路径。
该变量被初始化为从环境变量 PYTHONPATH 获取的默认路径,或者如果 PYTHONPATH 未设置,则从内置默认路径初始化。
你可以使用标准列表操作对其进行修改:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
dir()
内置函数 dir() 用于查找模块定义的名称。
它返回一个排序过的字符串列表:
基础测试
>>> dir(fibo)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']
无参
如果没有参数,dir() 会列出你当前定义的名称:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2', 'fibo']
显示内置函数和变量的名称
dir() 不会列出内置函数和变量的名称。
如果你想要这些,它们的定义是在标准模块 builtins 中:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
参考资料
https://docs.python.org/zh-cn/3/tutorial/modules.html