本部分主要介绍 Python 模块和包的一些基础知识。阅读本文预计需要 10 min。
1. 前言
代码 --> 函数 – > 模块 --> 包,是一个由简单到复杂,不断抽象的过程。设计这些都是为了方便我们管理和维护我们的代码。当代码越来越多,利用模块和包来管理 Python 代码就非常有必要了。本文主要介绍:
- 模块(定义、导入、.pyc 文件、搜索路径、dir()、pip)
- 包(定义、绝对导入、相对导入)
2. 模块
模块(module):在 Python 中一个 xxx.py
文件就是一个模块,其中,xxx
就模块名。如:test.py
模块的模块名就是 test
。
使用模块主要有以下好处:
- 提高了代码的可维护性。
- 可以更好地复用代码。
- 可以避免函数名、类名、变量名冲突。在不同模块中,出现同名函数和变量,也不会导致冲突。
命名模块名时要注意:
- 模块名的命名方式遵循变量名命名规则,不要出现中文和特殊字符。
- 不要和内置模块重名。可以在 Python 交互模式下
import xxx
,检验xxx
模块是否是内置模块冲突。
如:
>>> import collections
>>> import abc
>>> import apple
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'apple'
可以发现,collections
和 abc
都被成功导入,说明它们是内置模块。不能再作为自定义的模块名,而 apple 导入失败,说明 apple 不是内置模块,可以作为自定义的模块名。
2.1 模块的创建
下面创建一个 hello.py
模块
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"a test module"
__author__ = "Jock"
import sys
def test():
args = sys.argv
if len(args) == 1:
print(f"打印sys.argv:{sys.argv}")
print("Hello world")
elif len(args) == 2:
print(f"打印sys.argv:{sys.argv}")
print(f"Hello, {args[1]}")
else:
print(f"打印sys.argv:{sys.argv}")
print("Too many arguments")
if __name__ == '__main__':
test()
说明:
- 第 1 行和第 2 行是标准注释,第 1 行可以让这个 hello.py 模块直接在 Unix/Linux/Mac 上运行,第 2 行注释表示,
.py
文件本身使用标准的UTF-8
编码。 - 第 4 行是一个字符串,表示模块的文档注释,说明模块功能。任何模块代码的第一个字符串都被视为模块的文档注释。
- 第 6 行使用
__author__
变量把作者写进去,方便别人瞻仰你的大名。
以上部分就是 Python 模块的标准文件模板,你也可以全部删掉,不过按照标准做事准没错。
在上面的 hello.py 模块中 import sys
是导入 sys 模块。sys 是一个标准模块,我们要使用它,必须先用 import 导入。导入后,sys 变量就会指向 sys 模块,通过这个 sys 变量就可以访问 sys 模块的所有功能。
sys 模块中的 args 变量会把命令行的所有参数存为一个 list,并且第一个元素是 .py
文件名。我们可以在命令行下测试如下:
(base) C:\Users\Jock\Desktop\Python_Code>python test.py
打印sys.argv:['test.py']
Hello world
(base) C:\Users\Jock\Desktop\Python_Code>python test.py Jock
打印sys.argv:['test.py', 'Jock']
Hello, Jock
(base) C:\Users\Jock\Desktop\Python_Code>python test.py Jock Kobe
打印sys.argv:['test.py', 'Jock', 'Kobe']
Too many arguments
最后解释一下以下两行代码。
if __name__ == '__main__':
test()
模块名可以通过 xxx.__name__
来获取,如 hello.py 模块,那么 hello.__name__
将返回模块名 hello
。但是,当我们把 hello.py 模块放到命名行执行时,即:python hello.py
,这时 Python 解释器把 __name__
赋值为 __main__
,而如果在其他地方导入该 Hello 模块时,if 判断将失败。通过添加 if __name__ == '__main__':
可以让一个模块通过命令行运行时,执行一些额外代码。
这样的设计经常用于为模块提供一个方便的用户接口,或用于模块测试。
如果我们启动交互模式,再导入 hello 模块。
(base) C:\Users\Jock\Desktop\Python_Code>python
Python 3.7.6 (default, Jan 8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
这里并没有打印 Hello world,因为模块被导入时,模块名为 hello,所以if __name__ == '__main__':
判断为假,后面的代码 test()
不会执行。只有调用 hello.test() 时才会打印 Hello world。
>>> hello.test()
打印sys.argv:['']
Hello world
我们可以发现 `hello.test() 写起来有点麻烦,如果经常使用这个函数,可以把它赋值给一个局部变量。
>>> test = hello.test
>>> test()
打印sys.argv:['']
Hello world
2.2 模块的导入方式
导入模块的方式有两种:
- import module_name
- from module_name import A
前面已经用过 import module_name
的形式导入模块,这种方法会把整个模块导入。如果我们只想导入模块的一部分,那么我们可以用 from A import B
的形式,它表示从模块 A 中导入需要的 B,B 可以是类或者函数或者变量等,其余模块部分我们不需要就不导入,从而节省内存,避免命名冲突等。以上面的 hello 模块为例:
>>> from hello import test
>>> test()
打印sys.argv:['']
Hello world
如果有时候模块 A 或者导入的 B 名字太长,我们写起来很麻烦,这时可以用 as
来设置别名,简化调用,如:
>>> import hello as h
>>> h.test()
打印sys.argv:['']
Hello world
>>> from hello import test as t
>>> t()
打印sys.argv:['']
Hello world
其实我们也可以用 from A import *
的形式把模块 A 全部导入,但是一般不推荐这么做,因为这可能会带来副作用,会降低导入速度,导致命名冲突。
注意:出于效率的考虑,每个模块在每个解释器会话中只被导入一次。因此,如果更改了模块,则必须重新启动解释器,或者,如果只是一个要交互式地测试模块,可以使用 importlib.reload(),例如 import importlib; importlib.reload(modulename)。
2.3 模块的作用域问题
在一个模块中,我们可以定义很多函数和变量,我们希望一部分函数和变量给别人调用,有一部分函数和变量仅限于模块内部使用。Python 中可以通过 _
前缀来实现仅限模块内部使用。
我们希望给别人调用的函数和变量定义为公开的(public),如:test、get、PI
等。
类似于 __xxx__
这样的变量是特殊变量,可以直接被引用,但是有特殊用途,比如前面的 __author__
、__name__
等就是特殊变量。模块文档注释可以通过 __doc__
特殊变量访问,自定义的变量一般不要用这样的形式定义。
类似于 _xx
和 __xx
这样的函数或变量是私有的(private),不应该被直接引用,如:_abc
,__abc
等。
举个栗子:
def _private_1(name):
return f"Hello, {name}"
def _private_2(name):
return f"Hi, {name}"
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
在这个模块中,我们公开 greeting()函数,给外界调用,而把内部逻辑用 private 函数隐藏起来。这其实就是一种代码封装和抽象的方法,调用者只要知道调用,不用了解具体的实现。即:外部不需要引用的函数全部定义为 private,只有外部需要的函数才定义为 public。
2.4 模块搜索路径
当我们导入一个模块时,Python 会从指定的路径下搜索对应的模块,如果没有找到,就报错。
>>> import good
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'good'
通常情况下,Python 解释器会搜索当前目录,所有已安装的内置模块和第三方模块,搜索路径存放在 sys 模块的 path 变量中。
>>> import sys
>>> sys.path
['', 'C:\\Users\\Jock\\anaconda3\\python37.zip', 'C:\\Users\\Jock\\anaconda3\\DLLs', 'C:\\Users\\Jock\\anaconda3\\lib', 'C:\\Users\\Jock\\anaconda3', 'C:\\Users\\Jock\\anaconda3\\lib\\si
te-packages', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\win32', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\Pyth
onwin']
我们可以通过两种方式添加自己的搜索目录:
- 直接修改 sys.path,添加要搜索的变量:
>>> import sys
>>> sys.path.append("C:\\Users\\Jock\\mylib")
>>> sys.path
['', 'C:\\Users\\Jock\\anaconda3\\python37.zip', 'C:\\Users\\Jock\\anaconda3\\DLLs', 'C:\\Users\\Jock\\anaconda3\\lib', 'C:\\Users\\Jock\\anaconda3', 'C:\\Users\\Jock\\anaconda3\\lib\\si
te-packages', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\win32', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\Jock\\anaconda3\\lib\\site-packages\\Pyth
onwin', 'C:\\Users\\Jock\\mylib']
- 第二种方法是设置环境变量,环境变量中的内容会被自动添加到模块搜索路径中。
2.5 “编译过的” Python 文件
为了加速模块载入,Python 会自动在 __pycache__
目录里,缓存每个模块编译后的版本,名称为 module.version.pyc
,这就是.pyc
文件的由来。其中 version 一般使用 Python 版本号。例如,在 CPython 版本 3.7 中,spam.py 模块的编译版本将被缓存为 __pycache__/spam.cpython-37.pyc
。此命名约定允许来自不同发行版和不同版本的 Python 的已编译模块共存。
Python 通过检查源模块的修改日期,来确定模块的编译版本是否已过期,如果过期就会重新编译,并缓存。这是一个完全自动化的过程。此外,编译的模块(.pyc
文件)与平台无关,因此可以在具有不同操作系统之间共享。
Python 在两种情况下不会检查缓存。首先,对于从命令行直接载入的模块,它从来都是重新编译并且不存储编译结果;其次,如果没有源模块,它不会检查缓存。为了支持无源文件(仅编译)发行版本, 编译模块必须是在源目录下,并且绝对不能有源模块。
Python 官网给专业人士的一些小建议:
- 你可以在 Python 命令中使用 -O 或者 -OO 开关, 以减小编译后模块的大小。 -O 开关去除断言语句,-OO 开关同时去除断言语句和
__doc__
字符串。由于有些程序可能依赖于这些,你应当只在清楚自己在做什么时才使用这个选项。“优化过的”模块有一个 opt- 标签并且通常小些。将来的发行版本或许会更改优化的效果。 - 引入
.pyc
文件的目的是为了加快载入速度,并不会影响执行速度,.pyc
文件和 源文件执行的速度是一样的。 - compileall 模块可以为一个目录下的所有模块创建
.pyc
文件。 - 更多的细节可以参看,PEP 3147。
2.6 dir()函数
内置函数 dir() 用于查找模块定义的名称。 它返回一个排序过的字符串列表。
>>> import hello
>>> dir(hello)
['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']
如果没有参数,dir() 会列出当前定义的名称:
>>> a = [1, 2, 3, 4, 5]
>>> import hello
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'hello']
注意:它列出变量、模块、函数等所有类型的名称。
但 dir() 不会列出内置函数和变量的名称。如果要列出这些,可以这么做:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', ...省略... 'type', 'vars', 'zip']
2.7 安装模块
Python 有非常强大的第三方库,在 Python 中我们可以用包管理工具 pip 来完成安装第三方模块。如:安装 requests 库,直接在命令行用命令:pip install requests
安装即可。
此外,我们可以直接安装 Anaconda,它内置了非常多的第三方模块,非常强大,这样我们就不用一个一个的去安装。
3. 包
如果有很多模块需要管理,这时候包(package)就派上用场了。包是按目录来组织管理模块的。
举个栗子:
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
...
注意目录下必须有 __init__.py
文件,这样这个目录才会被 Python 当成包,否则 Python 会把它当做一个普通目录。在最简单的情况下,__init__.py
可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__
变量。__init__.py
本身就是一个模块,它的模块名是相应的目录名。如:formats 目录下的 __init__.py
模块名就是 formats
。多级层次的包结构中,echo.py 文件的模块名是 sound.effects.echo
我们可以导入包中的一个模块,如:
import sound.effects.echo
这会加载子模块 sound.effects.echo 。但引用它时必须使用它的全名。
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
还可以用另一种方法导入子模块,并使其在没有包前缀的情况下可用:
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)
另一种形式是直接导入所需的函数或变量,如:导入函数 echofilter():
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
小结:
- 当使用
from package import item
时,item
可以是包的子包,也可以是包中定义的模块,甚至函数、类和变量。
不推荐使用 from sound.effects import *
的形式导入整个包,因为这会使得导入花费很长时间,而且还可能要有副作用。
唯一的解决方案是让包作者提供一个包的显式索引。import 语句使用下面的规范:如果一个包的 __init__.py
代码定义了一个名为 __all__
的列表,它会被视为在遇到 from package import *
时应该导入的模块名列表。例如,文件 sound/effects/__init__.py
包含以下代码:__all__ = ["echo", "surround", "reverse"]
。这意味着 from sound.effects import *
将导入 sound
包的三个命名子模块。这部分还需要留作以后深究。
总之, 使用 from package import specific_submodule
没有任何问题,也是推荐的做法。
有时我们会见到一些相对导入。
from . import echo
from .. import formats
from ..filters import equalizer
说明:.
代表当前包,..
代表上一级包,...
代表上上级包。
请注意,相对导入是基于当前模块的名称进行导入的。由于主模块的名称总是 '__main__'
,因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。
一些规范:
- import 语句总是放在文件开头的地方。
- 引入模块的时候,from math import sqrt 比 import math 更好。
- 如果有多个 import 语句,应该将其分为三部分,从上到下分别是 Python 标准模块、第三方模块和自定义模块,每个部分内部应该按照模块名称的字母表顺序来排列。
4. 巨人的肩膀
推荐阅读:
- 编程小白安装Python开发环境及PyCharm的基本用法
- 一文了解Python基础知识
- 一文了解Python数据结构
- 一文了解Python流程控制
- 一文了解Python函数
- 一文了解Python部分高级特性
如果我的笔记对你有所帮助,欢迎关注我的微信公众号,加我好友,一起学习进步!
转载:https://blog.csdn.net/qq_27283619/article/details/105956548