[译]The Python Tutorial#Modules

6. Modules

如果你从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, end=' ')
        a, b = b, a+b
    print()

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 More on Modules

模块可以同时包含可执行语句和函数定义。可执行语句用来初始化模块。当模块名字出现在导入语句中时,这些可执行语句只执行一次[1]。(如果文件作为脚本,这些可执行也会执行)

每一个模块都有自己私有的符号表,这个符号表被所有定义在模块中的函数作为全局符号表使用。因此,模块的作者可以使用这些全局变量,而不用担心和用于全局变量偶然的名字冲突。另一方面,如果你知道自己在做什么,你可以使用与引用函数相同的方法来引用模块的全局变量,modname.itemname

模块可以引用其他模块。将所有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

这种方法导入所有不以下划线_开头的名字。大多数情况下,Python程序员不会使用这种方法,因为这会导入未知的名字集合到解释器中,也许还会屏蔽已经定义的一些名字。

需要注意,通常在实践中从模块或者包中导入所以名字是不鼓励使用的,因为会降低程序的易读性。然而,在交互式环境中使用它来减少打字输入是可行的。

注意: 出于性能原因,一个解释器会话中每个模块只导入一次。因此,如果模块被改变了,必须重启解释器;如果只想要交互式测试一个模块,使用 importlib.reload(),例如:import importlib; importlin.reload(modulename)

6.1.1 Executing modules as scripts

当使用以下命令运行Python模块:

python fibo.py <arguments>

模块中的代码会被执行,就像导入该模块一样,但是这时__name__被设置为__main__。这意味着以下代码会加入到模块末尾:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

模块即作为脚本执行,也可以作为模块导入,因为只有当模块作为mian文件执行时候,解析命令行的代码才会执行:

$ python fibo.py 50
1 1 2 3 5 8 13 21 34

如果模块被导入,代码不会执行:

>>> import fibo
>>>

这可以用来为使用者提供一个模块用户接口的使用约定,也可以用作测试(模块作为脚本时执行测试用例)

6.1.2 The Module Search Path

当模块spam被导入时,解释器首先搜索built-in模块。如果没有找到,解释器在变量sys.path提供的路径列表中搜索名为spam.py的文件。sys.path从下列位置初始化:

  • 包含输入脚本的目录(或者没有指定文件时的当前目录)
  • PYTHONPATH(目录名字集合,与shell环境变量PATH相似,也是环境变量)
  • 安装默认目录

注意: 在支持符号链接的文件系统,包含输入脚本的目录是符号链接指向的目录。也就是说包含符号链接的目录不会被加入到搜索路径中。

初始化后,Python程序可以修改sys.path。包含执行脚本的目录被放置到搜索路径的开始,在标准库路径之前。这意味着该目录中的脚本会被加载,而标准库目录中的同名模块不会被加载。这会引发错误,除非是有意替换标准库的模块。阅读Standard Modules获取更多信息。 (译注:自定义的模块不应与标准模块重名,否则标准模块会被覆盖。)

6.1.3 “Compiled” Python files

为加速模块加载,Python会在__pycache__目录中缓存每个模块的编译版本,缓存文件名为module.version.pycversion编码了被编译文件的版本;通常包含了Python的版本号。例如,在CPython release 3.3中,文件spam.py的编译版本会被缓存为__pycache__/spam.cpython-33.pyc。这种命名约定允许来自不同Python发行版本的模块得以共存。

Python检查源文件的修改日期与编译版本,来确定编译版本是否过期,是否需要重新编译。这是一个完全自动化的过程。另外,编译模块是平台独立的,因此异构系统可以共享相同的库。

Python不会检查在两个环境中的缓存。首先,Python总是重新编译,并且不会存储直接从命令行加载的模块的结果。其次,如果没有源模块,Python不检查缓存。若要支持无源文件(只有编译版本)分布,那么编译的模块必须放在源文件目录中,并且源模块必需不存在。

给专家的建议:

  • 可以在命令行使用-O或者-OO开关来减少编译模块的大小。-O参数移除assert语句,-OO参数同时移除assert语句和__doc__字符串。由于一些程序依赖这些变量,那么只有当你确认你要这么做时,才能使用这两个参数。“优化的”模块有一个opt-标签并且通常更小。未来的发型版本可能改变优化的影响。
  • .pyc文件中读取的程序不会比从.py文件读取的程序跑得快;.pyc文件快的地方在于加载。
  • compileall模块可以为目录中的所有模块创建.pyc文件。
  • PEP 3147中有关系这点的更多信息,包括一个决策流程

6.2 Standard Modules

Python提供了标准模块库,在独立文档中描述,名为Python Library Reference(以后叫做Library Reference)。有一些模块内嵌入解释器中,这些模块不是语言核心的一部分,但是它们是内嵌的,这既是为性能考虑,也提供了访问如系统调用般的操作系统原生接口的方式。这些模块集合依赖底层平台的配置选项。例如winreg模块只在Windows系统中提供。一个特殊的模块值得注意:sys,这个模块内嵌在所有Python解释器中。变量sys.ps1sys.ps2定义了主提示符和辅助提示符的字符串:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

只有当解释器以交互模式运行才会定义这两个变量。

变量sys.path是决定解释器模块搜索路径的字符列表。该变量从环境变量PYTHONPATH,或者内置默认路径(PYTHONPATH未指定时)初始化。可以使用标准list操作修改它的值:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3 The dir() Function

内嵌函数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 Packages

包是使用“圆点模块名”结构化Python模块名字空间的方式。例如,模块名A.B表示包A中的子模块B。就像模块使得不同模块的作者免于担忧每个模块的全局名字一样,圆点模块名的使用使得多模块包(如NumPy或者Python图像库)的作者免于担忧每个模块的名字。

假设需要为统一音频文件和音频数据的处理设计一个模块的集合(包)。有许多不同的音频文件格式(通常通过扩展名辨认,如.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)

注意当使用from package import item时,item可以使子模块(子包),也可以是包内定义的其他名字,如函数,类或者变量。import语句首先测试要导入的项是否在包中存在;如果不存在,Python假设这个项是模块并尝试加载它。如果最后寻找失败,ImportError异常抛出。

相对的,使用import item.subitem.subsubitem时,除了最后一项,每一项都必须是包;最后一项可以是模块或者包但是不能是前面的项中定义的类,函数或者变量。

6.4.1 Importing * From a Package

使用from sound.effects import *会发生什么?理想情况下,总是期望在文件系统中找出所有子模块,并全部导入。全部导入会耗费很长时间,并且导入子模块可能会有不期待的副作用,这些副作用应该在显式导入时发生。

唯一的解决方案是包作者提供一个包的显式索引。import语句遵循以下约定:如果包的__init__.py代码中定义了名为__all__的变量,那么使用from package import *时会导入该变量指定的所有模块。当包的新版本发布时,由包作者负责更新列表__all__。如果包作者不希望可以使用from package import *导入包中的模块,也可以不支持__all__。 例如,文件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语句显式加载的任意包子模块。考虑如下代码:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

这个例子中,echosurround模块被导入到当前名字空间中,因为执行from... import时,它们已经定义在sound.effects包中定义了。(定义了__all__时同样有效)

尽管使用import *时只有符合特定模式的名字会被导出,但仍然不建议在生产代码中使用。

记住,使用form Package import specific_submodle没有错误。实际上,这是推荐的方法,除非当前模块需要使用其他包中的同名模块。

6.4.2 Intra-package References

当包中包含了子包结构(就如例子中的sound包),可以使用绝对导入的方式引用兄弟包中的子模块。例如,如果模块sound.filters.vocoder需要使用包sound.effects中的echo模块,可以使用from sound.effects import echo

也可以使用from modul import name语句来相对导入模块。这种方式使用点.指示当前包和相对导入中涉及的父包。以surround模块为例:

from . import echo
from .. import formats
from ..filters import equalizer

相对导入基于当前模块的名字。由于主模块的名字总是__main__,要当做Python应用主模块使用的模块必须总是使用绝对导入的方式。

6.4.3 Packages in Multiple Directories

包还支持一个特殊属性,__path__。在__init__.py中的代码执行之前,属性__path__被初始化为一个列表,这个列表包含了持有该__init__.py文件的目录的路径。该变量可修改,这样做会影响将来的对包内模块和子包的搜索。

然而这个特性不总是需要的,它可以用来扩展包的模块集合

Footnotes

[1] 实际上,函数定义也是可“执行”的“语句”;模块级别的函数执行将函数的名字放置到模块的全局符号表中

posted on 2017-06-16 15:48  珞樱缤纷  阅读(453)  评论(0编辑  收藏  举报