引用/导入模块或包的详解(6)python入门教程
如果您退出 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解释器并使用以下命令导入此模块:
>>> >>> import fibo
这不会直接在当前符号表中输入定义的函数的名称fibo
;它只在fibo
那里输入模块名称。使用模块名称,您可以访问功能:
>>> >>> 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
6.1 更多关于模块
一个模块可以包含可执行语句以及函数定义。这些语句旨在初始化模块。它们仅在 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
这不会在本地符号表中引入从中获取导入的模块名称(因此在示例中,fibo
未定义)。
甚至还有一个变体可以导入模块定义的所有名称:
>>> >>> from fibo import * >>> fib(500) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这将导入所有名称,但以下划线 ( _
) 开头的名称除外。在大多数情况下,Python 程序员不使用这个工具,因为它在解释器中引入了一组未知的名称,可能隐藏了一些你已经定义的东西。
请注意,通常不赞成*
从模块或包导入的做法,因为它通常会导致代码可读性差。但是,可以使用它来节省交互式会话中的输入。
如果模块名称后面跟着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
笔记
出于效率原因,每个模块在每个解释器会话中只导入一次。因此,如果你改变你的模块,你必须重新启动解释器——或者,如果它只是你想要交互测试的一个模块,使用importlib.reload()
,例如。import importlib; importlib.reload(modulename)
6.1.1 作为脚本执行模块
当您运行 Python 模块时
python fibo.py <arguments>
模块中的代码将被执行,就像您导入它一样,但__name__
设置为"__main__"
. 这意味着通过在模块末尾添加此代码:
if __name__ == "__main__":
import sys fib(int(sys.argv[1]))
您可以使该文件可用作脚本以及可导入模块,因为解析命令行的代码仅在模块作为“主”文件执行时运行:
$ python fibo.py
50 0 1 1 2 3 5 8 13 21 34
如果导入模块,则不运行代码:
>>> >>> import fibo >>>
6.1.2 模块搜索路径
当spam
导入一个名为的模块时,解释器首先搜索具有该名称的内置模块。spam.py
如果没有找到,它会在由变量 给定的目录列表中 搜索一个文件sys.path
。 sys.path
从这些位置初始化:
- 包含输入脚本的目录(或未指定文件时的当前目录)。
PYTHONPATH
(目录名称列表,语法与 shell 变量相同PATH
)。- 安装相关的默认值。
笔记:
在支持符号链接的文件系统上,包含输入脚本的目录是在符号链接之后计算的。换句话说,包含符号链接的目录不会添加到模块搜索路径中。
sys.path
. 包含正在运行的脚本的目录位于搜索路径的开头,位于标准库路径之前。这意味着将加载该目录中的脚本,而不是库目录中的同名模块。除非打算更换,否则这是一个错误。6.1.3 “编译”的 Python 文件
为了加快加载模块,Python将每个模块的编译版本缓存在__pycache__
name下的目录中,其中版本编码了编译文件的格式;它通常包含 Python 版本号。例如,在 CPython 3.3 版中,spam.py 的编译版本将被缓存为. 这种命名约定允许来自不同版本和不同 Python 版本的编译模块共存。module.
version.pyc
__pycache__/spam.cpython-33.pyc
Python 根据编译后的版本检查源代码的修改日期,以查看它是否已过时并需要重新编译。这是一个完全自动的过程。此外,编译后的模块与平台无关,因此可以在不同架构的系统之间共享同一个库。
Python 在两种情况下不检查缓存。首先,它总是重新编译并且不存储直接从命令行加载的模块的结果。其次,如果没有源模块,它不会检查缓存。要支持非源(仅编译)分发,编译模块必须在源目录中,并且不能有源模块。
给专家的一些提示:
- 您可以使用 Python 命令上的
-O
或-OO
开关来减小已编译模块的大小。-O
开关删除断言语句,开关-OO
删除断言语句和 __doc__ 字符串。由于某些程序可能依赖这些可用的程序,因此只有在您知道自己在做什么的情况下才应该使用此选项。“优化”模块有一个opt-
标签,通常更小。未来的版本可能会改变优化的效果。 - 从文件中读取程序的运行速度并不
.pyc
比从文件中读取时快.py
。文件唯一更快的.pyc
是它们的加载速度。 - 该模块
compileall
可以为目录中的所有模块创建 .pyc 文件。 - 有关此过程的更多详细信息,包括决策流程图。
6.2. 标准模块
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
, 或者从一个内置的默认值 if PYTHONPATH
未设置。您可以使用标准列表操作对其进行修改:
>>> >>> import sys >>> sys.path.append('/ufs/guido/lib/python')
6.3. dir()
函数
内置函数dir()
用于找出模块定义的名称。它返回一个排序的字符串列表:
>>> >>> import fibo, sys >>> dir(fibo) ['__name__', 'fib', 'fib2'] >>> dir(sys) ['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__', '__package__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe', '_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', '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', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', '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']
6.4 包
包是一种通过使用“带点的模块名称”来构建 Python 模块名称空间的方式。例如,模块名称A.B
指定一个名为B
的包中的子模块A
。就像使用模块让不同模块的作者不必担心彼此的全局变量名一样,使用点分模块名称可以让 NumPy 或 Pillow 等多模块包的作者不必担心彼此的模块名.
假设您要设计一组模块(“包”)来统一处理声音文件和声音数据。有许多不同的声音文件格式(通常通过它们的扩展名来识别,例如:、、、.wav
) .aiff
,.au
因此您可能需要创建和维护不断增长的模块集合,以便在各种文件格式之间进行转换。您可能还想对声音数据执行许多不同的操作(例如混合、添加回声、应用均衡器功能、创建人工立体声效果),因此您将编写一个永无止境的模块流来执行这些操作。这是您的包的可能结构(以分层文件系统表示):
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
这将加载子模块sound.effects.echo
。它必须以其全名引用。
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
导入子模块的另一种方法是:
from sound.effects import echo
这也加载了 submodule echo
,并使其在没有包前缀的情况下可用,因此可以按如下方式使用:
echo.echofilter(input, output, delay=0.7, atten=4)
另一个变体是直接导入所需的函数或变量:
from sound.effects.echo import echofilter
同样,这会加载 submodule echo
,但这会使其功能 echofilter()
直接可用:
echofilter(input, output, delay=0.7, atten=4)
请注意,使用 时,item 可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数、类或变量。该语句首先测试该项目是否在包中定义;如果不是,它假定它是一个模块并尝试加载它。如果找不到它, 则会引发异常。from package import item
import
ImportError
相反,当使用类似的语法时,除了最后一项之外的每一项都必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量。import item.subitem.subsubitem
6.4.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
如果 __all__ 没有定义,则 from sound.effects import * 语句不会将 sound.effects 包中的所有子模块导入当前命名空间; 它只确保包 sound.effects 已被导入(可能在 __init__.py 中运行任何初始化代码),然后导入包中定义的任何名称。 这包括由 __init__.py 定义的任何名称(以及显式加载的子模块)。 它还包括先前导入语句显式加载的包的任何子模块。 考虑这段代码:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
echo
和surround
模块被导入到当前命名空间中,因为它们是在执行语句sound.effects
时在包中定义的。from...import
(这在 __all__
定义时也有效。)尽管某些模块设计为仅在您使用时导出遵循某些模式的名称,但在生产代码中仍然被认为是不好的做法。import *
请记住,使用没有任何问题!事实上,这是推荐的表示法,除非导入模块需要使用来自不同包的同名子模块。from package import specific_submodule
6.4.2 包内引用
sound
示例中的包),您可以使用绝对导入来引用兄弟包的子模块。例如,如果模块sound.filters.vocoder
需要使用包echo
中的模块sound.effects
,则可以使用.from sound.effects import echo
你也可以写相对导入,用import语句的形式。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,从 模块中,您可以使用:from module import name
surround