Python教程(五)——模块
目录
- 1. 引言
- 2. 模块详解
- 2.1 以脚本方式执行模块
- 2.2 模块搜索路径
- 2.3 已编译的Python文件
- 3. 标准模块
- 4. dir()函数
- 5. 包
- 5.1 从包中导入\*
- 5.2 相对导入
- 5.3 多目录中的包
- 参考
1. 引言
退出Python解释器后,再次进入时,之前在Python解释器中定义的函数和变量就丢失了。因此,编写较长程序时,最好用文本编辑器代替解释器,执行文件中的输入内容,这就是编写脚本。随着程序越来越长,为了方便维护,最好把脚本拆分成多个文件。编写脚本还有一个好处,不同程序调用一个函数时,不用把函数定义复制到各个程序。
为实现这些需求,Python把各种定义存入到一个文件,在脚本或解释器的交互式实例中使用。这个文件就是模块;模块中的定义可以导入到其他模块或主模块。
模块是包含Python定义和语句的文件,其文件名是模块名加后缀名.py。在模块内部,通过全局变量__name__可以获取模块名。例如,用文本编辑器在当前目录下创建fibo.py文件,输入以下内容:
# 斐波那契数列模块def fib(n): # 打印斐波那契数列直到na, b = 0, 1while a < n:print(a, end=" ")print()def fib2(n): # 返回斐波那契数列直到nresult = []a, b = 0, 1while a < n:result.append(a)a, b = b, a + breturn result
在当前目录打开命令行,进行Python解释器,用以下命令导入模块:
>>> import fibo
此操作不会直接把fibo中定义的函数名称添加到当前namespace中(后面会解释namespace);它只是将模块名称fibo添加到namespace。使用该模块名称就可以访问其中的函数:
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果经常使用某个函数,可以把它赋值给局部变量:
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
2. 模块详解
模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在import语句第一次遇到模块名时执行。(文件作为脚本运行时,也会执行这些语句)
每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。因此,模块作者可以在模块内部使用全局变量而不担心与用户定义的全局变量发生意外冲突。另一方面,如果你知道要怎么做就可以通过与引用模块函数一样的标记法modname.itemname来访问一个模块的全局变量。
模块可以导入其他模块。根据惯例可以将所有import语句都在放在模块的开头但这并非强制要求。如果被放置于一个模块的最高层级,则被导入的模块名称会被添加到该模块的全局命名空间。
还有一种import语句的变化形式可以将来自某个模块的名称直接导入到导入模块的命名空间中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这条语句不会将所导入的模块的名称引入到局部命名空间中。
还有一种变体可以导入模块内定义的所有名称:
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这种方式会导入所有不以下划线开头的名称。大多数情况下,不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称。
注意,一般情况下,不建议从模块或包内导入*。
模块名后使用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中也可以使用这种方式,效果类似:
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
2.1 以脚本方式执行模块
可以用以下方式运行Python模块:
python fibo.py <arguments>
这项操作将执行模块里的代码,和导入模块一样,但会把__name__赋值为"main"。也就是把下列代码添加到模块末尾:
if __name__ == "__main__":import sysfib(int(sys.argv[1]))
这个文件既能被用作脚本,又能被用作一个可供导入的模块,因为解析命令行参数的那两行代码只有在模块作为“main"文件执行时才会运行:
python fibo.py 50
0 1 1 2 3 5 8 13 21 34
当这个模块被导入到其他模块时,那两行代码不运行:
>>> import fibo
>>>
这常用于为模块提供一个便捷的用户接口,或用于测试。
2.2 模块搜索路径
当导入一个名为spam的模块时,解释器首先会搜索具有该名称的内置模块。这些模块的名称在sys.builtin_module_names中列出。如果未找到,它将在变量sys.path所给出的目录列表中搜索名为spam.py的文件。sys.path是从这些位置初始化的:
1. 被命令行直接运行脚本所在的目录(或未指定文件时的当前目录)。
2. PYTHONPATH(目录列表,与shell变量PATH的语法一样)。
3. 依赖于安装的默认值(按照惯例包括一个site-packages目录,由site模块处理)
备注:在支持符号链接的文件系统中,“被命令行直接运行的脚本所在的目录”是符号链接最终指向的目录。换句话说,符号链接所在的目录并没有被添加到模块搜索路径。
初始化后,Python程序可以更改sys.path。脚本所在的目录优先于标准库所在的路径被搜索。这意味着,脚本所在的目录如果由和标准库同名的文件,那么加载的是该目录里的,而不是标准库的。这一般是一个错误,除非这样的替换是你有意为之。
2.3 已编译的Python文件
为了快速加载模块,Python把模块的编译版本缓存在__pycache__目录中,文件名为module.version.pyc,version对编译文件格式进行编码,一般是Python的版本号。例如,CPython的3.3发行版中,spam.py的编译版本缓存为__pycache__/spam.cpython-33.pyc。这种命名惯例让不同Python版本编译的模块可以共存。
Python对比编译版与源码的修改日期,查看编译版是否已过期,是否需要重新编译。此进程完全是自动的。此外,编译模块与平台无关,因此,可在不同架构的系统之间共享相同的库。
Python在两种情况下不检查缓存:
1. 从命令行直接载入的模块,每次都会重新编译,且不储存编译结果。
2. 没有源模块,就不会检查缓存。
为了让一个库能以隐藏源代码的形式分发,编译后的模块必须放在源目录而非缓存目录中,并且源目录绝不能包含同名的未编译的源模块。
给专业人士的一些小建议:
1. 在Python命令中使用-O或-OO开关,可以减小编译模块的大小。-O去除断言语句,-OO去除断言语句和__doc__字符串。有些程序可能依赖于这些内容,因此,没有十足的把握,不要使用这两个选项。优化过的模块带有opt-标签,并且文件通常会小一些。
2. 从.pyc文件读取的程序不比从.py读取的执行速度快,.pyc文件只是加载速度更快。
3. compileall模块可以为一个目录下的所有模块创建.pyc文件。
3. 标准模块
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是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量PYTHONPATH提取的默认路径进行初始化,如未设置PYTHONPATH,则使用内置的默认路径。可以用标准列表操作修改该变量:
>>> import os
>>> sys.path.append('/ufs/guido/lib/python')
4. dir()函数
内置函数dir()用于查找模块定义的名称。返回结果是经过排序的字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__','__interactivehook__', '__loader__', '__name__', '__package__', '__spec__','__stderr__', '__stdin__', '__stdout__', '__unraisablehook__','_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework','_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook','api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix','breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing','callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info','excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info','float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth','getallocatedblocks', 'getdefaultencoding', 'getdlopenflags','getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile','getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval','gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info','intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value','maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks','path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix','set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags','setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr','stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info','warnoptions']
没有参数时,dir()列出当前已定义的名称:
>>> a = [ 1, 2, 3, 4, 5 ]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
注意它列出所有类型的名称:变量、函数、模块,······。
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', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented','NotImplementedError', 'OSError', 'OverflowError','PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError','ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning','StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError','SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError','UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError','UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning','ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__','__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs','all', 'any', 'ascii', 'bin', 'bool', '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']
5. 包
包是通过使用“带点号模块名”来构造Python模块命名空间的一种方式。例如,模块名A.B表示为A的包中名为B的子模块。就像使用模块可以让不同模块的作者不必担心彼此的全局变量名一样,使用带点号模块名也可以让Numpy或Pillow等多模块包的作者也不必担心彼此的模块名冲突。
假设要为统一处理声音文件与声音数据设计一个模块集(包)。声音文件的格式很多(通常以扩展名来识别,例如:.wav,.aiff,.au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。下面这个分级文件树展示了这个包的架构:
sound/ 最高层级的包__init__.py 初始化sound包formats/ 用于文件格式转换的子包__init__.py wavread.pywavwrite.pyaiffread.pyaiffwrite.pyauread.pyauwrite.py···effects/ 用于音效的子包__init__.pyecho.pysurround.pyreverse.py···filters/ 用于过滤器的子包__init__.pyequalizer.pyvocoder.pykaraoke.py···
导入包时,Python搜索sys.path里的目录,查找包的子目录。
需要有__init__.py文件才能让Python将包含该文件的目录当作包来处理(除非使用namespace pakcage,这是一个相对高级的特性)。这可以防止重名的目录如string在无意中屏蔽后继出现在模块搜索路径中的有效模块。在最简单的情况下,init.py可以只是一个空文件,但它也可以执行包的初始化代码或设置__all__变量,这将在稍后详细描述。
还可以从包中导入单个模块,例如:
import sound.effects.echo
这也会加载子模块echo,并使其不必加包前缀,因此可按如下方式使用:
echo.echofilter(input, output, delay=0.7, atten=4)
import语句的另一种变体是直接导入所需的函数或变量:
from sound.efffects.echo import echofilter
同样,这将加载子模块echo,但这使其函数echofilter()直接可用。
注意,使用from package import item时,item可以是包的子模块或子包,也可以是包中定义的函数、类或变量等其他名称。import语句首先测试包中是否定义了item;如果未在包中定义,则假定item是模块,并尝试加载。如果找不到item,则触发ImportError异常。
相反,使用import item.subitem.subsubitem句法时,除最后一项外,每个item必须是包,最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。
5.1 从包中导入*
使用from sound.effects import *会发生什么?你可能希望它会查找并导入包的所有子模块,但事实并非如此。因为这将花费很长的时间,并且可能会产生你不想要的副作用,如果这种副作用被你设计为只有在导入某个特定的子模块时才应该发生。
唯一的解决办法是提供包的显式索引。import语句使用如下惯例:如果包的__init__.py代码定义了列表__all__,运行from package import *时,它就是被导入的模块名列表。发布包的新版本时,包的作者应更新此列表。如果包的作者认为没有必要在包中执行导入*操作,也可以不提供此列表。例如,sound/effects/__init__.py文件可以包含以下代码:
__all__ = [ "echo", "surround", "reverse" ]
这意味着from sound.effects import *将导入sound.effects包的三个命名子模块。
请注意子模块可能会受到本地定义名称的影响。例如,如果你在sound/effects/__init__.py文件中添加了一个reverse函数,from sound.effects import *将只导入echo和surround这两个子模块,但不会导入reverse子模块,因为它被本地定义的reverse函数所遮挡:
__all__ = ["echo", # 指向echo.py文件"surround", # 指向surround.py文件"reverse" # !!! 现在指向reverse函数
]def reverse(msg: str): # <-- 此名称将覆盖reverse.py子模块return msg[::-1] # 针对from sound.effects import *的情况
如果没有定义__all__,from sound.effects import *语句不会把包sound.effects中的所有子模块都导入到当前命名空间;它只是确保包sound.effects已被导入(可能还会运行__init__.py中的任何初始化代码),然后导入包中定义的任何名称。这包括由__init__.py定义的任何名称(以及显式加载的子模块)。它还包括先前import语句显式加载的包里的任何子模块。例如:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在本例中,echo和surround模块被导入到当前命名空间,因为在执行from … import语句时它们已在sound.effects包中定义了(当定义了__all__时也是如此)。
虽然,可以把模块设计为用import *时只导出遵循指定模式的名称,但仍不提倡在生产代码中使用这种做法。
记住,使用from package import specific_submodule没有任何问题!实际上,除了导入模块使用不同包的同名子模块之外,这种方式是推荐做法。
5.2 相对导入
当包由多个子包构成时,可以使用绝对导入来引用同级包的子模块。例如,如果sound.filters.vocoder模块需要使用sound.effects包中的echo模块,它可以使用from sound.effects import echo。
你还可以编写相对导入代码,即使用from module import name形式的import语句。这些导入使用前导点号来表示相对导入所涉及的当前包和上级包。例如对于surround模块,可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
注意,相对导入基于当前模块名。因为主模块名永远是“main",所以如果计划将一个模块用作Python应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入(如果你想在一个模块中使用if __name__ == "__main__"来测试当前模块,该模块中的所有导入必须使用绝对导入)。
5.3 多目录中的包
包还支持一个特殊的属性__path__。在执行该文件中的代码之前,它被初始化为字符串的sequence,其中包含包的__init__.py的目录名称。这个变量可以修改;修改后影响今后对模块和包中包含子包的搜索。
这个功能虽然不常用,但可用于扩展包中的模块集。
参考
https://docs.python.org/zh-cn/3.13/tutorial/modules.html