第六章模块 python导引编译之七

第六章 模块

标题6.模块 Modules

如果你从python解释器退出来,又再次进入到python,你在python中定义好的东西就全丢失了。因此,如果你想写点更长些的程序,那就最好使用文本编辑器去做准备,然后再对解释器输入你准备好的文件。这就是所谓的创建一个脚本(script),而为人们熟知。当你的程序相当的长,你也许希望把它分为若干个文件,以便于保存和维护。你也许还希望,使用你已经在另外一些程序中写好的一些手边的函数,而无需复制其定义放进程序之中。
为了支持这个想法,python有办法把定义放进一个文件中,并在一个脚本中运用这些定义,或者在解释器的交互实例中运用。这样一类文件就被称为
模块(module)
;来自一个模块的定义可以导入进另外的模块或者导入进*主(main)*模块(变元的集合,你在一个脚本中已经登录的变元集合在顶端层次并且在计算模式中执行)。
一个模块是一个文件,该文件含有定义和陈述。该文件名就是带有添加后缀.py的模块名字。在一个模块之中,模块的名称(一个字符串)作为全域变元_name_的值是可用的,使用你喜欢的文本编辑器创建的一个文件称作fibo.py,在当前目录中后随以下内容:

def fib(n):    # 编写斐波拉契系列到n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

def fib2(n):    # 返回斐波拉契系列到n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

现在进入python解释器,并且导入这个模块,使用以下指令导入:

>>> import 6.1fibo.py

这并未直接进入在当前符号图表中的6.1.fibo所定义的函数名称;仅仅只是进入那里的模块名6.1.fibo。使用这个模块名,你就可以登录这个函数:(但要注意,文件要保存在正确的位置)

PS C:\Users\lenovo\1pthw0> py
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import 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]

如果你还想使用一个函数,这个函数常常是,你能把它指派给一个局域名称的函数:

>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

标题6.1.有关模块更多的内容 More on Modules

一个模块含有一些可执行的陈述,还包含函数的定义。这些陈述都意图初始化这个模块。这些陈述仅第一次在一个导入的陈述中处理面对的模块名称【1】(它们也运行,如果文件作为脚本被执行的话。)
每一个模块都有它自己的私人符号表,该表也被用作全域符号表,通过所有在模块中得到定义的那些函数。所以,一个模块的作者可以在模块里运用全域变元,而不用担心与使用者的全域变元发生冲突。另一方面,如果你知道,你正在做的东西,你就可以接触一个带有同样标记的模块变元,这个标记用来提及其函数;modname, itemname.
模块还可以导入另外的模块。在模块(或者脚本)的开头这是形成的习惯,但也没有要求放置所有的导入陈述。导入的模块名被安置在导入的模块的全域符号表中。
有一个导入陈述的变体,从模块导入的名称直接放进导入模块的符号表中。例如:

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

注意:因为充分的理由,每一模块在每一个解释器期间仅导入一次。所以,如果你改变了你的模块,你就必须重新启动解释器——或者,如果那正是你想交互测试的模块,就使用指令import.reload();例如import importlib;importlib;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])

你可以使得这个文件既作为脚本也作为一个导入的模块来用,因为分析了指令行的这个代码,如果该模块作为主文件被执行,它才运行:

>>> $ python fibo.py 50
  File "<stdin>", line 1
    $ python fibo.py 50

如果模块导入了,编码就不再运行:

>>> import fibo
>>>

这常被用来或是提供一个方便的对于模块的用户接口,或是为测试的目的(运行模块如同一个脚本处理一个测试套件一样)。

标题6.1.2. 模块寻找通道The Module Search Path

当一个名为spam的模块被导入时,解释器首先用其名称来寻找一个内置函数。如果没有发现目标,它就会接着寻找一个名为spam.py的文件,这个文件在一个由变元sys.path给定的目录列表中,sys.path从这些位置被初始化:
含有输入脚本的目录(或者当没有文件被指定的一个当前目录)
PYTHONPATH(一个目录名列表,带有同样的句法作为壳变元PATH)
依赖缺省安装

注意:在支持系统链接的文件系统中,含有输入脚本的目录在系统连接跟随之后得到计算。换句话说,含有系统链接的目录不增加到那个寻找path的模块之中。

初始化之后,python程序可以修改sys.path。含有那个还在运行的脚本的目录被放置在寻找通道的开端,标准图书馆通道之前。这意味着在目录中的脚本将被装载替代图书馆目录中同样名称的模块。这是一个错误,除非替代被要求。更多信息请看标准模块部分。

标题6.1.3. 编辑python文件“Compiled” Python files

为加速装载模块,python 储存了每一个模块的编辑版,储存在名为module.version.pyc之下的目录_pycache_之中。名为module.version.pyc的这个目录中,该版编码了那个编辑文件的格式;该格式一般都含有那个python的版数。例如,在发布为3.3的CPython中,spam.py的那个编辑版会储存为_pycache_/spam.cpython-33.pyc。这种命名习惯允许编辑好的模块,来自于共同存在的各种不同时间发布的不同版次的python。
python在两种情形中不检查这种储存。第一种,python总是重新编辑并不储存该模块的结果,该模块直接接受指令装载。第二种,如果没有原模块,python也不检查这个储存。为了支持一个非原分布(仅仅是编辑的),这个编辑模块必须是在原本的目录中,并且那里一定没有原模块。

对专业人士的几点小提示:
你可以使用指令-0或者-00打开python指令去减少一个编辑模块的规模。那个-0开关迁移走了断定陈述,而那个-00开关既迁移走了断定陈述,还迁移走了_doc_字符串。因为有些程序也许依赖这些可用的东西,如果你知道你要做的事情,你就应该使用这种选择。优选的模块有一个优选图标opt-,它通常要更小一些。未来发布的版次也许会改变这种优选的效果。
当一个程序是用.py文件来读取时,把这个程序的读取和用.pyc文件来读取加以比较,后者并不会比前者快;要让.pyc文件读取更快,唯一可行的办法是速度,这些文件被装载的速度。
模块compileall可以为所有在一个目录中的模块,创建.pyc文件。
在这个步骤之上,还有更多的细节,包括判定的一个流量表,在PEP 3147之中。

标题6.2.标准模块 Standard Modules

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,或者来自内置缺省的通道,如果PYTHONPATH不是一个集合的话。但你可以运用标准列表操作来修改这个变元。

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

注意:这里需要退出C>,重新再进一次解释器。

标题6.3.目录函数 The dir() Function

内置函数dir()被用作找到名为模块定义的每一个函数。使用这个dir函数会返回一个字符串的分类列表如下:

C> quit()
PS C:\Users\lenovo> py
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append('/ufs/quido/lib/python')
>>> import fibo, sys
>>> dir(fibo)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__', '_base_executable', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', '_xoptions', 'addaudithook', 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info', 'warnoptions', 'winver']
>>>

若没有参数,dir()就列出了你已经定义过的那些名称:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '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', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', '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指定了一个名称为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.path函数上用来寻找封装子目录的那个目录来寻找这个封装。
这个__init__.py文件被要求去使得python处理含有作为封装文件的那个目录。这防止了带有通名的目录,例如字符串,无意隐藏的有效模块,这样的模块会在寻找模块通道中,后来就出现了。在最简单的情形中,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)

注意当使用从封装导入项的导入时,那个项目也可以是该封装的子模块(或者是子封装),或者是其它在封装中定义过的名称,类似一个函数,类或者变元。那个导入陈述首先测试在封装中,该项是否得到定义,如果没有定义,它就假定这是一个模块,并意图去装载。如果找不到这个模块,就会出现一个导入例外ImportError的信息。
反之,当用一个类似import item.subsubitem这样的句法时,每一个项,除了最后一个,必须是一个封装。那个最后的项可以是一个模块或者一个封装,但是不能是一个类或者一个函数,或者一个在前述项中定义过的变元。

标题6.4.1. 从一个封装中导入星号* Importing * From a Package

当用户编写from sound.effects 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_submodules完全没有错! 事实上,除非输入模式需要使用子模块,而且这些子模块还需要来自不同封装的同样的名称,上述用法就是推荐的标识性用法。

标题6.4.2.内封装参考 Intra-package References

当封装构造进子封装的时候(如实例中那个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.多目录中的封装 Packages in Multiple Directories

封装支持一个更多特指属性的函数,path。它被初始化为一个列表,该列表含有目录的名称,而这个目录在那个文件被处理中的编码之前,含有封装的__init__.py。这个变元可以修改,若这样修改了,就影响到后来对于模块的寻找,也影响到在该封装中的子封装的寻找。
当这个属性并不是常常需要时,可以用来扩展在一个封装中发现的模块集合。

脚注
【1】事实上,函数定义也是被处理的陈述;模块层次函数的处理在模块全域符号表中进到该函数名。

猜你喜欢

转载自blog.csdn.net/weixin_41670255/article/details/108962557
今日推荐