企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### 6. 模块 如果你退出 Python 解释器并重新进入,你做的任何定义(变量和方法)都会丢失。因此,如果你想要编写一些更大的程序,最好使用文本编辑器先编写好,然后运行这个文件。这就是所谓的创建 *脚本*。随着你的程序变得越来越长,你可能想要将它分成几个文件,这样更易于维护。你还可能想在几个程序中使用你已经编写好的函数,而不用把函数拷贝到每个程序中。 为了支持这个功能,Python 有种方法可以把你定义的内容放到一个文件中,然后在脚本或者交互方式中使用。这种文件称为*模块*;模块中的定义可以 *导入* 到其它模块或 *主模块* 中。 模块是包含 Python 定义和声明的文件。文件名就是模块名加上.py 后缀。在模块里面,模块的名字(是一个字符串)可以由全局变量 __name__ 的值得到。例如,用你喜欢的文本编辑器在当前目录下创建一个名为 fibo.py 的文件,内容如下: ~~~ # Fibonacci numbers module def fib(n): # write Fibonacci series up to n a, b = 0, 1 while b < n: print b, a, b = b, a+b def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result ~~~ 现在进入 Python 解释器并使用下面的命令导入这个模块: ~~~ >>> import fibo ~~~ 这不会直接把 fibo 中定义的函数的名字导入当前的符号表中;它只会把模块名字 fibo 导入其中。你可以通过模块名访问这些函数: ~~~ >>> fibo.fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> fibo.fib2(100) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] >>> fibo.__name__ 'fibo' ~~~ 如果你打算频繁使用一个函数,可以将它赋给一个本地的变量: ~~~ >>> fib = fibo.fib >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ~~~ ### 6.1. 深入模块 模块可以包含可执行语句以及函数的定义。这些语句通常用于初始化模块。它们只在 *第一次* 导入时执行。[[1]](#)(如果文件以脚本的方式执行,它们也会运行。) 每个模块都有自己的私有符号表,模块内定义的所有函数用其作为全局符号表。因此,模块的作者可以在模块里使用全局变量,而不用担心与某个用户的全局变量有冲突。另一方面,如果你知道自己在做什么,你可以使用引用模块函数的表示法访问模块的全局变量,modname.itemname。 模块中可以导入其它模块。习惯上将所有的 [import](#) 语句放在模块(或者脚本)的开始,但这不是强制性的。被导入的模块的名字放在导入模块的全局符号表中。 [import](#) 语句的一个变体直接从被导入的模块中导入名字到导入模块的符号表中。例如: ~~~ >>> from fibo import fib, fib2 >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ~~~ 这不会把模块名导入到本地的符号表中(所以在本例中,fibo 将没有定义)。 还有种方式可以导入模块中定义的所有名字: ~~~ >>> from fibo import * >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ~~~ 这种方式不会导入以下划线 (_) 开头的名称。 注意一般情况下不赞成从一个模块或包中导入 * ,因为这通常会导致代码很难读。不过,在交互式会话中这样用是可以的,它可以让你少敲一些代码。 注意 出于性能考虑,每个模块在每个解释器会话中只导入一遍。因此,如果你修改了你的模块,你必需重新启动解释器 —— 或者,如果你就是想交互式的测试这么一个模块,可以使用[reload()](# "reload"),例如reload(modulename)。 #### 6.1.1. 执行模块 当你用下面的方式运行一个 Python 模块 ~~~ python fibo.py <arguments> ~~~ 模块中的代码将会被执行,就像导入它一样,不过此时 __name__ 被设置为 "__main__" 。也就是说,如果你在模块后加入如下代码: ~~~ if __name__ == "__main__": import sys fib(int(sys.argv[1])) ~~~ 就可以让此文件既可以作为可执行的脚本,也可以当作可以导入的模块,因为解析命令行的那部分代码只有在模块作为 “main” 文件执行时才被调用: ~~~ $ python fibo.py 50 1 1 2 3 5 8 13 21 34 ~~~ 如果模块是被导入的,将不会运行这段代码: ~~~ >>> import fibo >>> ~~~ 这种方法通常用来为模块提供一个方便的用户接口,或者用来测试(例如直接运行脚本会执行一组测试用例)。 #### 6.1.2. 模块搜索路径 当导入一个名为 spam 的模块时,解释器首先搜索具有该名称的内置模块。如果没有找到,它会接着到 [sys.path](# "sys.path") 变量给出的目录中查找名为 spam.py 的文件。[sys.path](# "sys.path") 变量的初始值来自这些位置: - 脚本所在的目录(或当前目录)。 - [PYTHONPATH](#) (一个包含目录名的列表,与 shell 变量PATH 的语法相同)。 - 与安装相关的默认值。 初始化后,Python 程序可以修改[sys.path](# "sys.path")。脚本所在的目录被放置在搜索路径的最开始,也就是在标准库的路径之前。这意味着将会加载当前目录中的脚本,库目录中具有相同名称的模块不会被加载。除非你是有意想替换标准库,否则这应该被当成是一个错误。更多信息请参阅 [*标准模块*](#) 小节。 #### 6.1.3. "编译过的" Python 文件 对于使用了大量标准模块的简短程序,有一个提高启动速度的重要方法,如果在spam.py所在的目录下存在一个名为spam.pyc的文件,它会被视为spam模块的已“编译”版本。生成spam.pyc文件时,spam.py文件的修改时间会被记录在spam.pyc文件中,如果时间不匹配,.pyc文件将被忽略。 通常情况下,您不需要做任何事情来创建spam.pyc文件。每当spam.py编译成功,会尝试向spam.pyc写入编译的版本。尝试失败也不会出现错误;如果出于任何原因文件写入不完全,生成的 spam.pyc 文件将被当作是无效的,并且在以后会被忽略。spam.pyc文件的内容与平台无关,因此Python模块的目录可以在不同体系结构的机器间共享。 部分高级技巧: - 当以[*-O*](#)标志调用Python解释器时,将生成优化的代码并保存在.pyo文件中。目前的优化不会帮助太多;它只是删除[assert](#)语句。当使用[*-O*](#)时,将优化*所有*的[*字节码*](#);将忽略.pyc文件并将.py文件编译成优化的字节码。 - 向 Python 解释器传递两个[*-O*](#)标志 ([*-OO*](#)) 会导致字节码编译器执行优化,极少数情况下,这可能导致程序故障。目前只会从字节码中删除__doc__字符串,使.pyo文件更加紧凑。因为某些程序可能会依赖于具有这些可用,您应只使用此选项,在你知道你在做什么时。 - 程序不能运行得更快,不管它是从哪种文件(.py ; .pyc或.pyo)中读取。唯一加速的是.pyc或.pyo文件的加载速度。 - 当在命令行使用脚本名称来运行脚本时,脚本的字节码是不会被写入相应的.pyc或.pyo文件中的。因此,如果将大部分与启动无关的代码移到一个模块中而以导入模块的方式在启动脚本中导入这个模块就可节省一些启动时间。也可以直接在命令行上运行.pyc或.pyo文件。 - 可以没有相同的模块文件spam.py时,使用文件spam.pyc (或spam.pyo时使用[*-O*](#) )。这可用于发布不太容易进行反向工程的Python 代码库。 - 模块[compileall](# "compileall: Tools for byte-compiling all Python source files in a directory tree.")可以为一个目录中的所有模块创建.pyc文件 (或.pyo文件时使用[*-O*](#) )。 ### 6.2. 标准模块 Python 带有一个标准模块库,并发布有单独的文档叫Python 库参考手册(以下简称"库参考手册")。有些模块被直接构建在解析器里;这些操作虽然不是语言核心的部分,但是依然被内建进来,一方面是效率的原因,另一方面是为了提供访问操作系统原语如系统调用的功能。这些模块是可配置的,也取决于底层的平台。例如,winreg模块只在Windows系统上提供。有一个特别的模块值得注意:[sys](# "sys: Access system-specific parameters and functions."),它内置在每一个Python解析器中。变量sys.ps1和sys.ps2定义了主提示符和辅助提示符使用的字符串: ~~~ >>> import sys >>> sys.ps1 '>>> ' >>> sys.ps2 '... ' >>> sys.ps1 = 'C> ' C> print 'Yuck!' Yuck! C> ~~~ 只有在交互式模式中,这两个变量才有定义。 变量 sys.path 是一个字符串列表,它决定了解释器搜索模块的路径。它初始的默认路径来自于环境变量 [PYTHONPATH](#),如果 [PYTHONPATH](#) 未设置则来自于内置的默认值。你可以使用标准的列表操作修改它: ~~~ >>> import sys >>> sys.path.append('/ufs/guido/lib/python') ~~~ ### 6.3. [dir()](# "dir")函数 内置函数 [dir()](# "dir") 用来找出模块中定义了哪些名字。它返回一个排好序的字符串列表: ~~~ >>> import fibo, sys >>> dir(fibo) ['__name__', 'fib', 'fib2'] >>> dir(sys) ['__displayhook__', '__doc__', '__excepthook__', '__name__', '__package__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_getframe', '_mercurial', 'api_version', 'argv', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_clear', 'exc_info', 'exc_traceback', 'exc_type', 'exc_value', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'gettotalrefcount', 'gettrace', 'hexversion', 'long_info', 'maxint', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'py3kwarning', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'subversion', 'version', 'version_info', 'warnoptions'] ~~~ 如果不带参数, [dir()](# "dir") 列出当前已定义的名称: ~~~ >>> a = [1, 2, 3, 4, 5] >>> import fibo >>> fib = fibo.fib >>> dir() ['__builtins__', '__name__', '__package__', 'a', 'fib', 'fibo', 'sys'] ~~~ 注意它列出了所有类型的名称: 变量、 模块、 函数等。 [dir()](# "dir")不会列出内置的函数和变量的名称。如果你想列出这些内容,它们定义在标准模块 [__builtin__](# "__builtin__: The module that provides the built-in namespace.") 中: ~~~ >>> import __builtin__ >>> dir(__builtin__) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip'] ~~~ ### 6.4. 包 包是一种管理 Python 模块命名空间的方式,采用“点分模块名称”。例如,模块名 A.B 表示包A 中一个名为 B 的子模块。就像模块的使用让不同模块的作者不用担心相互间的全局变量名称一样,点分模块的使用让包含多个模块的包(例如 Numpy 和 Python Imaging Library)的作者也不用担心相互之间的模块重名。 假设你想要设计一系列模块(或一个“包”)来统一处理声音文件和声音数据。现存很多种不同的声音文件格式 (通常由它们的扩展名来识别,例如: .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 ... ~~~ 导入这个包时,Python 搜索 sys.path 中的目录以寻找这个包的子目录。 为了让 Python 将目录当做包,目录下必须包含 __init__.py 文件;这样做是为了防止一个具有常见名字(例如 string)的目录无意中隐藏目录搜索路径中正确的模块。最简单的情况下,__init__.py 可以只是一个空的文件,但它也可以为包执行初始化代码或设置__all__变量(稍后会介绍)。 用户可以从包中导入单独的模块,例如: ~~~ import sound.effects.echo ~~~ 这样就加载了子模块sound.effects.echo。它必须使用其完整名称来引用。 ~~~ sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) ~~~ 导入子模块的另一方法是: ~~~ from sound.effects import echo ~~~ 这同样也加载了子模块echo,但使它可以不用包前缀访问,因此它可以按如下方式使用: ~~~ echo.echofilter(input, output, delay=0.7, atten=4) ~~~ 还有另一种变化方式是直接导入所需的函数或变量: ~~~ from sound.effects.echo import echofilter ~~~ 这再次加载了子模块echo,但这种方式使函数echofilter() 可以直接访问: ~~~ echofilter(input, output, delay=0.7, atten=4) ~~~ 注意使用frompackageimportitem时,item 可以是包的子模块(或子包),也可以是包中定义的一些其它的名称,比如函数、 类或者变量。import语句首先测试 item 在包中是否有定义;如果没有,它假定它是一个模块,并尝试加载它。如果未能找到,则引发[ImportError](# "exceptions.ImportError")异常。 相反,使用类似 importitem.subitem.subsubitem 这样的语法时,除了最后一项其它每项必须是一个包;最后一项可以是一个模块或一个包,但不能是在前一个项目中定义的类、函数或变量。 #### 6.4.1. 从包中导入 * 那么现在如果用户写成 fromsound.effectsimport* 会发生什么?理想情况下,他应该是希望到文件系统中寻找包里面有哪些子模块,并把它们全部导入进来。这可能需要很长时间,而且导入子模块可能会产生想不到的副作用,这些作用本应该只有当子模块是显式导入时才会发生。 唯一的解决办法是包的作者为包提供显式的索引。[import](#) 语句使用以下约定: 如果包中的 __init__.py 代码定义了一个名为__all__的列表,那么在遇到 frompackageimport*语句的时候,应该把这个列表中的所有模块名字导入。当包有新版本包发布时,就需要包的作者更新这个列表了。如果包的作者认为不可以用 import * 方式导入它们的包,也可以决定不支持它。例如,文件sound/effects/__init__.py可以包含下面的代码: ~~~ __all__ = ["echo", "surround", "reverse"] ~~~ 这意味着 fromsound.effectsimport* 将导入sound 包的三个子模块。 如果 __all__ 没有定义,fromsound.effectsimport* 语句 *不* 会从 sound.effects 包中导入所有的子模块到当前命名空间;它只保证 sound.effects 包已经被导入(可能会运行 __init__.py 中的任何初始化代码),然后导入包中定义的任何名称。这包括由 __init__.py 定义的任何名称(以及它显式加载的子模块)。还包括这个包中已经由前面的[import](#) 语句显式加载的子模块。请考虑此代码: ~~~ import sound.effects.echo import sound.effects.surround from sound.effects import * ~~~ 在这个例子中,执行 from...import 语句时,echo 和 surround 模块被导入到当前命名空间是因为它们在sound.effects中有定义。(定义了 __all__时也会同样工作。) 虽然某些模块设计成使用 import* 时只导出符合特定模式的名称,在产品代码中使用这种写法仍然是不好的做法。 记住,使用 fromPackageimportspecific_submodule 一点没错 !事实上,这是推荐的写法,除非导入的模块需要使用其它包中的同名子模块。 #### 6.4.2. 包内引用 子模块通常需要相互引用。例如,surround 模块可能会使用 echo 模块。事实上,这种引用是如此常见以致 [import](#) 语句会在标准模块搜索路径之前首先在所在的包中查找。因此,surround 模块可以简单地使用importecho 或 fromechoimportechofilter。如果在当前包(当前模块属于其子模块的包)中未找到要导入的模块,[import](#) 语句会查找具有给定名称的顶级模块。 如果一个包是子包(比如例子中的 sound 包),你可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder 需要使用sound.effects 包中的 echo 模块,它可以使用 fromsound.effectsimportecho。 Python 2.5 开始,除了上面描述的隐式相对导入,你可以使用frommoduleimportname 的导入形式编写显式相对导入。这些显式的相对导入使用前导的点号表示相对导入的是从当前包还是上级的包。以 surround 模块为例,你可以使用: ~~~ from . import echo from .. import formats from ..filters import equalizer ~~~ 注意,显式和隐式相对导入都基于当前模块的名称。因为主模块的名字总是 "__main__" ,Python 应用程序的主模块应该总是用绝对导入。 #### 6.4.3. 包含多个目录的包 包还支持一个特殊的属性, __path__。在文件运行之前,该变量被初始化为一个包含 __init__.py 所在目录的列表。这个变量可以修改;这样做会影响未来包中包含的模块和子包的搜索。 虽然通常不需要此功能,它可以用于扩展包中的模块的集合。 脚注 | [[1]](#) | 实际上,函数的定义也是‘执行过’的‘语句’;模块级别的函数定义的执行将函数名放入该模块的全局符号表中。 | |-----|-----|