Python教程之五-----模块

如果你退出Python解释器并且在此进入,你之前写的定义(函数和变量)将会丢失。因此,如果你想写一些更长的程序,你最好使用一个文本编辑器来准备解释器的输入并且运行那个文件来代替输入。这被称为创建一个脚本。当你的程序变的更长时,你也许想将它拆分成几个文件,以便维护。你也许想要使用一个在几个程序中写的方便的函数而不用复制它的定义到每一个程序中。


为了支持这种操作,Python有一种方法将定义放在一个文件中,并在一个脚本或者交互式解析器实例中使用它。这个文件就称为module(模块);模块中的定义可以导入到其他模块中,或者主模块中(在顶层执行的脚本和在计算模式中反问的变量的集合)。


模块是一个包含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 更多关于模块

模块可以包含可执行语句和函数定义。这些语句用来初始化模块。他们仅仅在一个导入语句中模块名称第一次出现时执行。(如果将文件作为脚本执行,也会运行)


每个模块都有自己的私有符号表,这个表被模块中定义的所有函数用作全局符号表。因此,模块的作者可以在模块中使用全局变量而不用担心和用户定义的全局变量有意外的冲突。另一方面,如果你知道你正要做什么,你可以使用一些指定它函数的语法来访问一个模块的全局变量,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

这将导入所有除了开头带下划线(_)的名称。大多数情况下Python程序员并不会这么做,因为它可能向解释器引入了一个未知的名称集合,可能隐藏了一些你一进定义过的东西。


注意,一般来说从一个模块或者包中导入*的这个动作是不被赞成的,因为它通常导致不可读的代码。然而,在家护士会话中用于保存输入还是可以的。


注意:因为一些效率原因,每一个模块在每一个解释器会话中只是导入一次。因此,如果你改变了模块,你必须重新启动解释器------或者,只有一个模块你想交互式的测试,可以使用importlib.reload(),例如:import importlib;importlib.reload (modulename)。


6.1.1 以脚本方式执行模块

当你以如下命令运行Python模块时:

python fibo.py <arguments>

模块中的代码将会被执行,就好象你已经导入过来,但是名称将会是__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从这些地方初始化:

  • 包含输入脚本的路径(或者当没有文件指定时当前路径)
  • PYTHONPATH(一个路径名称列表,有和shell变量PATH一样的语法)
  • 安装默认路径
注意,在支持符号链接的文件系统上,包含输入脚本的路径是在符号链接之后计算的。换句话说,包含符号链接的路径没有添加到模块的搜索路径

初始化后,Python程序能够修改sys.path。包含正在运行的脚本的路径位于搜索路径的最开始,在标准库路径的前面。这就意味着该路径中的脚本将会加载而不是库路径里相同名字的模块。除非你是故意这么做的,否则这将是一个错误。

6.1.3  编译Python文件

为了加速加载模块,Python会在名为module.version.pyc下的__pycache__路径下缓存每个模块的版本,这个路径编码编译后的文件的格式;一般它会包括Python的版本数字。例如在CPython发布版3.3里spam.py的编译版本将会缓存在__pycache__/spam.cpython-33.pyc。这个命名约束允许来自不同Python版本的编译模块和不同版本的Python共存。


Python检查编译版本的源文件的修改数据来看它是否过期并且需要重新编译。这是一个完全自动化的过程。同样,编译后的模块是平台独立的,所以,同样的库可以在拥有不同体系的系统中共享。


有2中情况Python不会去检查缓存。第一,它总是重新编译并且不存储直接来自于命令行的模块的结果。第二,如果没有源模块,它也不会检查缓存。为了支持非源(仅编译后)的分发,编译后的模块必须在源路径里,并且不能有一个源模块。


专家们的一些建议:

  • 你可以使用-o或者-oo开关在命令行中来减少编译后模块的大小。-o开关移除了断言语句,-oo开关移除了断言语句和__doc__字符串。因为一些程序依赖这些,只有你知道你在做什么你才能使用这些选项。优化的模块有一个opt-标签并且一般会更小。未来的版本可能会改变优化的效果。
  • 从一个.pyc文件读取比从一个.py文件读取不会让一个程序运行更快。唯一让.pyc文件更快运行的事情是它加载的速度。
  • 模块copileall能为所有模块在路径中创建.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>

这2个变量只在解释器是交互模式下才会定义。


变量sys.path是一个指定解释器的模块搜索路径字符串列表。它有环境变量PYTHONPATH来初始化一个默认路径,或者如果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指定了一个名为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路径上搜索来寻找包的字路径。


将目录看作包含的包需要__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语句首先测试是否item定义在包中;如果没有,它假设它是一个模块并尝试加载它,如果没有找到,将抛出importError异常。


相反,当使用语法:import item.subitem.subsubitem,除了最后一个item每一个item都必须是包;最后一个item可以是一个模块或者一个包但不能是一个函数或者变量,定义在先前的item中。

6.4.1 从包中导入*

当写入from sound.effects import *将发生什么?理想的,人们希望这某种情况下进入文件系统,找到包中出现的字模块,并且将它们全部导入。这将花费很长的时间并且导入子模块将会产生一个只发生在当字模块显示导入时的不必要的副作用。


唯一的解决方案是包的作者提供一个明确的包的索引。import语句有以下约束:如果一个包的__init__.py代码定义了一个名为__all__的列表,当遇到from package import *时,它应该被认为是应该导入的模块名称的列表。当一个包的心版本发布时,取决于包的作者来更新这个列表。包的作者也可以决定不支持它,如果他们的包中没有import *。例如:文件sound/effects/__init__.py能包含如下内容:

__all__ = ["echo", "surround", "reverse"]

这意味着from sound.effects import *将会导入sound包中的3个如上命名的字模块


如果__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 *
123

在这个例子中,echo和surround模块被导入到当前的命名空间因为他们是在sound.effects包中当from...import语句调用时定义的。(当__all__定义时同样有效)


尽管当你使用import *时某些模块被设计为仅仅导出某些特定模式的名称,但在实际的代码中,这被认为是不好的实例。


记住,使用from package import specific_submodule没有错!事实上,除非导入的模块需要使用来自不同包的相同名称的子模块,这都是推荐的做法。

6.4.2 包内引用

当包由子包组成是(如上例的sound包),你可以使用绝对引入来使用兄弟包的子模块。例如,如果模块sound.filters.vocoder需要使用sound.effects包内的echo模块,可以使用from sound.effects import echo


同样也可以用相对引入,使用这样的语句from module import name。这些导入使用引导点来指示涉及的当前包和父亲包。例如从surround模块,你可以这样:

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

注意相对引入基于当前模块的名称。因为主模块的名称总是"__main__",用于作为Python应用程序的主模块必须总是使用绝对导入。

6.4.3 多路径下的包

包支持另一个特殊的属性,__path__。它被初始化为一个包含支持包的__init__.py路径名称的列表,在这个文件的代码被执行之前。这个变量可以修改;这样做将影响包含在包内的模块和子包的搜索。


然而这个特性通常不会用到,它能用于扩展一个包内的模块集合。

没有更多推荐了,返回首页