文章目录
- 模型(Modules)
- 更多关于模型的内容(More on Modules)
- 像脚本一样执行模块(Executing modules as scripts)
- 模块搜索路径
- 编译的python文件
- 标准模型
- `dir()` 函数
- 包(Packages)
- 从包中导入`*` (Importing * From a Package)
- 包内引用
- 多重路径的包(Packages in Multiple Directories)
模型(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 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中定义的函数的名称直接添加到当前的命名空间中(有关更多细节,请参阅Python作用域和命名空间);它只添加了名为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
更多关于模型的内容(More on Modules)
模块可以包含可执行语句,也可以包含函数定义。这些语句用于初始化模块。只有在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
这与导入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
出于效率的考虑,每个模块在每个解释器会话中只被导入一次。因此,如果你改变了你的模块,你必须重新启动解释器-或者,如果它只是一个你想要交互测试的模块,使用importlib.reload()
,例如import importlib
;importlib.reload (modulename)
。
像脚本一样执行模块(Executing modules as scripts)
当你运行Python模块时
python fibo.py <arguments>
模块中的代码将被执行,就像你导入它一样,但是__name__
被设置为"__main__
"。这意味着通过在模块末尾添加以下代码:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
你可以让这个文件既可用作脚本,也可用作可导入的模块,因为解析命令行的代码只在模块作为“main”文件执行时才会运行:
>>> python fibo.py 50
0 1 1 2 3 5 8 13 21 34
如果模块被导入,代码不会运行:
>>> import fibo
>>>
这通常用于为模块提供方便的用户界面,或者用于测试目的(将模块作为执行测试套件的脚本运行)。
模块搜索路径
当导入名为spam
的模块时,解释器首先搜索具有该名称的内置模块。这些模块名称在sys.builtin_module_names
中列出。如果没有找到,它就在变量sys.path
给出的目录列表中搜索一个名为spam.py
的文件。sys.path
从以下位置初始化:
- 包含输入脚本的目录(或者没有指定文件时的当前目录)。
-
PYTHONPATH
(目录名列表,语法与shell变量PATH相同)。 - 依赖于安装的默认值(约定包括
site-packages
目录,由site
模块处理)。
初始化后,Python程序可以修改sys.path
。包含正在运行的脚本的目录被放置在搜索路径的开头,在标准库路径之前。这意味着该目录中的脚本将被加载,而不是库目录中的同名模块。除非有意替换,否则这是一个错误。有关更多信息,请参阅标准模块一节。
编译的python文件
为了加快加载模块的速度,Python将每个模块的编译版本缓存到名为module.version.pyc
的__pycache__
目录中,其中版本对编译文件的格式进行编码;它通常包含Python版本号。例如,在CPython版本3.3中,spam.py的编译版本将被缓存为__pycache__/spam.cpython-33.pyc。这种命名约定允许来自不同版本和不同版本的Python编译模块共存。
Python根据编译版本检查源代码的修改日期,以确定它是否过期,是否需要重新编译。这是一个完全自动的过程。此外,编译后的模块是平台独立的,因此可以在具有不同体系结构的系统之间共享相同的库。
Python在两种情况下不检查缓存。首先,它总是重新编译,而不存储直接从命令行加载的模块的结果。其次,如果没有源模块,它不会检查缓存。要支持非源代码(仅编译)发行版,编译后的模块必须在源目录中,且不能有源模块。
给专家的一些建议:
可以在Python命令上使用-O
或-OO
开关来减小已编译模块的大小。-O
开关删除断言语句,-OO
开关同时删除断言语句和__doc__
字符串。因为有些程序可能依赖于这些可用的选项,所以只有在知道自己在做什么时才应该使用此选项。“优化”模块有一个opt-
标签,通常更小。未来的版本可能会改变优化的效果。
从.pyc文件读取程序并不比从.py文件读取程序运行得更快;唯一的
标准模型
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/guido/lib/python')
dir()
函数
内置函数dir()用于查找模块定义的名称。它返回一个排序的字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
'__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
'__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
'_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
'breakpointhook', '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', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
'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', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
'warnoptions']
不带参数,dir()列出你当前定义的名称:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
注意,它列出了所有类型的名称:变量、模块、函数等。
dir()
不会列出内置函数和变量的名称。如果你想要它们的列表,它们在标准模块内置中定义:
>>> 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']
包(Packages)
包是一种使用“虚线模块名”来构造Python模块命名空间的方法。例如,模块名A.B
在名为A的包中指定了一个子模块B。就像使用模块可以让不同模块的作者不必担心彼此的全局变量名一样,使用虚线格式的模块名可以让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)
注意,当使用from package import item
时,item
可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数、类或变量。import语句首先测试项目是否在包中定义; 如果不是,则假定它是一个模块并尝试加载它。如果找不到,则引发ImportError
异常。
相反,当使用像import item.subitem.subsubitem
这样的语法时,除最后一项外,每一项必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类、函数或变量。
从包中导入*
(Importing * From a Package)
现在当用户写下from sound.effects import *
? 理想情况下,人们希望这以某种方式传递到文件系统,找到包中存在的子模块,并将它们全部导入。这可能会花费很长时间,而且导入子模块可能会产生不必要的副作用,这些副作用只在显式导入子模块时才会发生。
唯一的解决方案是包作者提供包的显式索引。import语句使用以下约定: 如果一个包的__init__.py
中定义了一个名为__all__
的列表,当执行from package import *
时会导入__all__
中的模型。
当发布包的新版本时,由包作者负责保持该列表的最新。包作者也可能决定不支持它,如果他们看不到从他们的包导入*
的用途。
例如, 文件 sound/effects/__init__.py
可能包含下列代码:
__all__ = ["echo", "surround", "reverse"]
这意味着from sound.effects import *
可能从sound.effect
中会导入如上三个子模块
如果没有定义__all__
,则语句from sound.effect import *
不会从sound.effect
包中导入所有子模块到当前命名空间。它只确保包sound.effect
已经导入(可能在__init__.py
中运行任何初始化代码),然后导入包中定义的任何名称。这包括由__init__.py
定义的任何名称(以及显式加载的子模块)。它还包括由前面的导入语句显式加载的包的任何子模块。考虑以下代码:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在本例中,当执行语句from...import
时, echo
和surround
模块被导入到当前命名空间中,因为它们是在sound.effects
中定义的。(这也适用于定义了__all__
的情况。)
尽管在使用import *
时,某些模块被设计成只导出遵循特定模式的名称,但在生产代码中,这仍然被认为是糟糕的实践。
记住,使用from package import specific_submodule
没有错!事实上,这是推荐的表示法,除非导入模块需要使用来自不同包的具有相同名称的子模块。
包内引用
当包被构造成子包时(如示例中的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应用程序的主模块的模块必须始终使用绝对导入。
多重路径的包(Packages in Multiple Directories)
包还支持一个特殊的属性__path__
。在执行包的__init__.py
文件中的代码之前,它被初始化为一个包含目录名称的列表。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。
虽然通常不需要这个特性,但可以使用它来扩展包中的模块集。