【4.0】模块
前言
因为Python
是解释型的,这就意味着如果你运行后退出再次运行,之前定义的变量及其相关数据都会丢失。因此如果你希望编写一些长的程序,则需要使用文本编辑器来进行,并将编辑好的python
代码作为输入运行。这种形式的文件被称为:脚本。
随着编写的内容越来越多,当你希望将其编写的内容拆分成几个不同类型的文件以方便维护;或者你希望经常使用自己的某个函数(方法),而不必要每次都编写一次代码内容。为了支持这些需求,python
可以将定义放在一个文件里,并在脚本中交互的时候它们。这种文件被称为:模块。
在我们编写的脚本中,可以导入不同的模块,以实现各种我们所需要的功能。
模块本质仍是一个包含python
语句的文件,其后缀为:.py
。在模块内部,模块名称可以通过全局变量__name__
的值获得。
例如,你可以在任意文件路径下,创建一个新的.py
文件,其代码内容如下所示:
1 | def PutText(): |
该.py
文件中仅仅定义了一个名称为PutText
的函数(方法),你可以通过如下代码在另一个.py
文件中,调用你定义的这个方法,代码示例:
1 | import sys |
如果你经常使用某个函数,你可以将其赋值为一个变量来更快的调用该函数,代码示例:
1 | fun = Test002.PutText |
更多关于模块的信息
每个模块都是独立的个体,它们包含了各自的函数或者变量的“表”,并不会与你现模块中的变量等发生冲突。我们可以通过import
关键词来导入其他模块,模块被导入后,就可以通过模块名称.变量/函数
来调用模块内的变量或者函数等。
从习惯上来说,所有的
import
语句都应该放在模块/脚本的开头
当然,我们也可以直接选择导入模块中的指定函数,代码示例:
1 | from Test002 import PutText |
此处,我将我的Test002
模块中的PutText
函数导入到了现模块中,这样可以直接使用PutText
来调用函数。需要注意的是,这样引入仅仅引入了PutText
,这意味着你并不能调用到Test002
这个模块中的其他内容。
你甚至可以使用如下代码,来直接引入指定模块中所有函数,代码示例:
1 | from Test002 import * |
这样python
会调入所有非下划线(_
)开头的名称。不过,在绝大多数情况下,并不会使用这种语句,因为它可能会导致引入模块中的名称覆盖了现模块中已经定义过的东西。
当然,你也可以通过使用关键词as
来为引入的指定模块中的名称做重命名,进行现模块绑定,这么说可能不太好理解,可以通过如下代码示例:
1 | from Test002 import PutText as output |
上述代码中,我使用了output
来绑定到导入的Test002
模块中的PutText
函数名称,这样在我希望调用PutText
函数的时候,就可以直接使用我绑定的名称output
来调用。
同样的,我们也可以使用as
关键字来重命名导入的模块,其代码类似如下所示:
1 | impotimport Test002 as newName |
注:每个模块只会在解释器中导入一次,如果你在执行中改变了你的模块内容,则必须重新启动解释器才能保证你改变的模块内容能正确调用。如果你仅仅是希望在改变后做测试,可以尝试使用
importlib.reload()
函数。
模块搜索路径
当你需要导入一个名称为Test001
的模块的时候,解释器首先寻找具有该名称的内置模块。如果没有找到,然后解释器从 sys.path
变量给出的目录列表里寻找名为 spam.py
的文件。sys.path
初始有这些目录地址:
- 包含输入脚本的目录(或者未指定文件时的当前目录)。
PYTHONPATH
(一个拓展目录名称的列表,它和shell变量PATH
有一样的语法)。- 取决于安装的默认设置
需要注意的是,你可以在运行脚本的时候更改
sys.path
的列表内容,但是通常来说建议是将新增的目录添加到列表的后面,而不是开头;否则可能会出现优先调用你新添加的目录模块,而不是旧的标准模块。
编译过的Pyhton
文件
为了加速模块载入,Python
在 __pycache__
目录里缓存了每个模块的编译后版本,名称为 module.*version*.pyc
,其中名称中的版本字段对编译文件的格式进行编码; 它一般使用Python
版本号。例如,我的Test002
会被命名为:Test002.cpython-38.pyc
。这种命名方式会允许不同版本的Python
已编译模块共存。
Python
根据编译版本检查源的修改日期,以查看它是否已过期并需要重新编译。这是一个完全自动化的过程。
Python在两种情况下不会检查缓存。首先,对于从命令行直接载入的模块,它从来都是重新编译并且不存储编译结果;其次,如果没有源模块,它不会检查缓存。为了支持无源文件(仅编译)发行版本, 编译模块必须是在源目录下,并且绝对不能有源模块。
标准模块
Python附带了一个标准模块库;一些模块内置于解释器中;它们提供对不属于语言核心但仍然内置的操作的访问,以提高效率或提供对系统调用等操作系统原语的访问。
这些模块的集合是一个配置选项,它也取决于底层平台。例如,winreg
模块只在Windows操作系统上提供。一个特别值得注意的模块 sys
,它被内嵌到每一个Python解释器中。变量 sys.ps1
和 sys.ps2
定义用作主要和辅助提示的字符串,例如sys.ps1
其是>>>
字符,sys.ps2
其是...
字符。
注:这两个变量只有在编译器是交互模式(命令行)下才被定义。
sys.path
变量是一个字符串列表,用于确定解释器的模块搜索路径。该变量被初始化为从环境变量 PYTHONPATH
获取的默认路径,或者如果 PYTHONPATH
未设置,则从内置默认路径初始化。你可以使用标准列表操作对其进行修改,代码示例:
1 | import sys |
dir()
函数
内置函数 dir()
用于查找模块定义的名称。 它返回一个排序过的字符串列表:
1 | import sys |
如果没有参数,dir()
会列出你当前定义的名称。
注:它列出所有类型的名称:变量,模块,函数,等等。
dir()
不会列出内置函数和变量的名称。如果你想要这些,它们的定义是在标准模块 builtins
中:
1 | import builtins |
包
包是一种通过用“带点号的模块名”来构造 Python 模块命名空间的方法。 例如,模块名 A.B
表示 A
包中名为 B
的子模块。
假设你想为声音文件和声音数据的统一处理,设计一个模块集合(一个“包”)。由于存在很多不同的声音文件格式(通常由它们的扩展名来识别,例如:.wav
, .aiff
, .au
),因此为了不同文件格式间的转换,你可能需要创建和维护一个不断增长的模块集合。 你可能还想对声音数据还做很多不同的处理(例如,混声,添加回声,使用均衡器功能,创造人工立体声效果), 因此为了实现这些处理,你将另外写一个无穷尽的模块流。这是你的包的可能结构(以分层文件系统的形式表示):
1 | sound/ Top-level package |
包必须要有 __init__.py
文件才能让 Python 将包含该文件的目录当作包。 这样可以防止具有通常名称例如 string
的目录在无意中隐藏稍后在模块搜索路径上出现的有效模块。 在最简单的情况下,__init__.py
可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__
变量,具体将会在后面说明。
包的用户可以从包中导入单个模块,代码示例:
1 | import sound.effects.echo |
这会加载子模块 sound.effects.echo
。但引用它时必须使用它的全名。
导入子模块的另一种方法是
1 | from sound.effects import echo |
这也会加载子模块 echo
,并使其在没有包前缀的情况下可用。
另一种形式是直接导入所需的函数或变量:
1 | from sound.effects.echo import echofilter |
同样,这也会加载子模块 echo
,但这会使其函数 echofilter()
直接可用。
注意,当使用 from package import item
时,item可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数,类或变量。 import
语句首先测试是否在包中定义了item;如果没有,它假定它是一个模块并尝试加载它。如果找不到它,则引发 ImportError
异常。
相反,当使用 import item.subitem.subsubitem
这样的语法时,除了最后一项之外的每一项都必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量。
从包中导入 *
当用户写 from sound.effects import *
会发生什么?理想情况下,人们希望这会以某种方式传递给文件系统,找到包中存在哪些子模块,并将它们全部导入。这可能需要很长时间,导入子模块可能会产生不必要的副作用,这种副作用只有在显式导入子模块时才会发生。
唯一的解决方案是让包作者提供一个包的显式索引。import
语句使用下面的规范:如果一个包的 __init__.py
代码定义了一个名为 __all__
的列表,它会被视为在遇到 from package import *
时应该导入的模块名列表。在发布该包的新版本时,包作者可以决定是否让此列表保持更新。包作者如果认为从他们的包中导入 * 的操作没有必要被使用,也可以决定不支持此列表。例如,文件 sound/effects/__init__.py
可以包含以下代码:
1 | __all__ = ["echo", "surround", "reverse"] |
这意味着 from sound.effects import *
将导入 sound
包的三个命名子模块。
如果没有定义 __all__
,from sound.effects import *
语句 不会 从包 sound.effects
中导入所有子模块到当前命名空间;它只确保导入了包 sound.effects
(可能运行任何在 __init__.py
中的初始化代码),然后导入包中定义的任何名称。 这包括 __init__.py
定义的任何名称(以及显式加载的子模块)。它还包括由之前的 import
语句显式加载的包的任何子模块。
子包参考
当包被构造成子包时(与示例中的 sound
包一样),你可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder
需要在 sound.effects
包中使用 echo
模块,它可以使用 from sound.effects import echo
。
你还可以使用import语句的 from module import name
形式编写相对导入。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,从 surround
模块,你可以使用:
1 | from . import echo |
注:相对导入是基于当前模块的名称进行导入的。由于主模块的名称总是
"__main__"
,因此用作Python应用程序主模块的模块必须始终使用绝对导入。
End
看起来有点冗杂,甚至有些难以理解,大概讲述了基本到模块调用,再到包结构和包的引用,可以不需要全部理解,用的时候慢慢也就能最后学到的