You are here:  Home » Python » 模块与包的详解(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语句中遇到模块名时执行。[1] (如果文件作为脚本执行,它们也会运行。)

每个模块都有自己的私有符号表,该表用作模块中定义的所有函数的全局符号表。因此,模块的作者可以在模块中使用全局变量,而不必担心与用户的全局变量的意外冲突。另一方面,如果您知道自己在做什么,则可以使用与其函数相同的符号来触摸模块的全局变量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 fibofib

当使用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)。
  • 依赖于安装的默认值。

注意

在支持符号链接的文件系统上,在遵循符号链接后计算包含输入脚本的目录。换句话说,包含符号链接的目录不会添加到模块搜索路径中。

初始化后,Python程序可以修改sys.path。包含正在运行的脚本的目录位于搜索路径的开头,位于标准库路径之前。这意味着将加载该目录中的脚本,而不是库目录中的同名模块。除非有意更换,否则这是一个错误。有关更多信息,请参见标准模块一节

6.1.3。“编译”的Python文件

为了加速加载模块,Python将每个模块的编译版本缓存在__pycache__名称下的目录中,其中版本对编译文件的格式进行编码; 它通常包含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文件。
  • 有关此过程的更多详细信息,包括决策的流程图 PEP 3147

 

6.2。标准模块

Python附带了一个标准模块库,在单独的文档Python库参考(以下简称“库参考”)中进行了描述。一些模块内置于解释器中; 这些操作提供对不属于语言核心但仍然内置的操作的访问,以提高效率或提供对系统调用等操作系统原语的访问。这些模块的集合是一个配置选项,它也取决于底层平台。例如,该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没有设定。您可以使用标准列表操作对其进行修改:

>>>
>>> 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 
        ...

 

导入时,Python会在目录中搜索 sys.pathpackage子目录。

__init__.py需要这些文件使Python将目录视为包含包; 这样做是为了防止具有通用名称的目录(例如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 itemimportImportError

相反,当使用语法时,除了最后一个项目之外的每个项目都必须是一个包; 最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量。

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.effectssound.effects__init__.py__init__.pyimport

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

 

在此示例中,echosurround模块将导入当前命名空间,因为它们是在执行语句sound.effects时在中定义的from...import。(这在__all__定义时也有效 。)

虽然某些模块设计为在使用时仅导出遵循某些模式的名称,但在生产代码中仍然被认为是不好的做法。import *

请记住,使用没有错!实际上,除非导入模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。fromPackage import specific_submodule

6.4.2。包内引用

当包被组织成子包时(与sound示例中的包一样),您可以使用绝对导入来引用兄弟包的子模块。例如,如果模块sound.filters.vocoder需要使用包中的echo模块sound.effects,则可以使用。from sound.effects import echo

您还可以使用import语句的形式编写相对导入。这些导入使用前导点来指示相对导入中涉及的当前和父包。 例如,从模块中,您可以使用:from module import namesurround

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

 

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

6.4.3。多个目录中的

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

虽然通常不需要此功能,但它可用于扩展程序包中的模块集。

脚注

[1] 实际上,函数定义也是“执行”的“语句”; 模块级函数定义的执行在模块的全局符号表中输入函数名。