1、何为模块
以下引用内容来自菜鸟教程,版权归菜鸟教程所有!
在前面的几个章节中我们脚本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。
为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py,模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。
简单来说,模块是一系列功能的集合体,可以看作工具箱,它也是有分门别类的:
- 内置模块,即Python解释器内置的模块!
- 自定义模块,即我们自己写的代码!
- 第三方模块,即别人封装好并共享出来的模块!
Python模块以四种形式存在:
- 用Python语言编写的.py文件
- 已被编译为共享库或DLL格式的C或C++扩展
- 将多个模块组织到一起的文件夹,文件夹下存在__init__.py文件,此文件夹可称为包
- 使用C编写并链接至Python解释器的内置模块
我们是新手上路,先学习Python编写的py文件和包即可!
2、为何用模块
先说内置、第三方模块!别问,问就是拿来即用,这样可以避免重复造轮子,省却大量工作,提升开发效率!下面举个例子:
import time
start_time = time.time()
time.sleep(5)
end_time = time.time()
print(f'这段代码沉睡了{end_time - start_time }秒' )
# 执行得到结果
这段代码沉睡了5.005227565765381秒
开瓶即饮,岂不美哉!
再说自定义模块,将代码提取出来存放于模块中提供共享服务,可减少代码冗余,提高重复使用率,代码结构更为清晰!可以想象一下,当你定义了一个函数,会被很多地方用到,难道你要一个一个地复制粘贴吗?显然不合理!这时候模块就派上用场了!
下面举个例子,假设我有三个py文件,test、test2、test3,其中test的这段代码会被所有程序文件所共用,如果没有模块功能,那么我们需要在每一个程序文件里面复制黏贴一次这段代码,这显然造成代码冗余!
def print_str():
print('我是test,公交出行,绿色环保!')
当我们把test.py作为模块后,事情就变得简单许多了,仅需要在每个要用到这段代码的程序文件中导入此模块即可!
import test
test.print_str()
print('我是test2,努力奋斗!')
# 执行得到结果
我是test,公交出行,绿色环保!
我是test2,努力奋斗!
3、如何用模块
3.1、import导入模块
Python文件首次导入模块会发生的事情(后续引用首次):
- 执行模块中代码
- 将执行过程中产生的名称都存放于该模块的名称空间中
- 在导入模块的当前文件中产生一个模块名称(如test.py,那么模块名就是test),该名称指向步骤2中产生的名称空间
# 简单改改test.py
def print_str():
print('我是test,公交出行,绿色环保!')
print('我是test,这是首次导入模块执行的')
test2导入test模块,可以看到test模块全局作用域的print代码执行在先,函数的局部print在后,为什么?很简单,前面已说过首次导入模块会执行模块,因导入在先引用在后,所以导致test中全局的print比函数中的print先执行!
import test
test.print_str()
# 执行得到结果
我是test,这是首次导入模块执行的
我是test,公交出行,绿色环保!
3.2、引用
上面例子中已展示引用模块的用法,即模块名+点+模块中的名称
3.3、import别名
还是上面的例子,假设我模块名称特别长,一次还好当多次引用模块时就显得很难看,针对这种情况我们可以用import...as...语法给它起别名(当然要注意别和当前文件名称混淆了)
import testttttttttttttttttt as test
test.print_str()
# 执行得到结果
我是test,公交出行,绿色环保!
3.4、自定义模块命名
自定义模块命名应该采用纯小写+下划线的风格
3.5、from导入
import方式导入模块后,须手动添加模块名称作为前缀并指定名称来调用,这样不会与当前名称空间混淆,缺点是麻烦!而使用from方式导入可以直接引用模块名称不需要加前缀!
# 在test.py中存在以下代码
def print_str():
print('哈哈')
def print_numer(a, b):
print(a + b)
#回到test2中用from导入并调用,import后面用*即代表导入所有名称,也可以使用__all__方法来控制*
from test import print_str, print_numer
print_str()
print_numer(33, 66)
# 执行得到结果
哈哈
99
3.6、from与import区别
简单对比上面已有讲述,from相比import代码更精简些,因为调用时不用加前缀;缺点是容易与当前名称空间混淆!
# test.py存在以下代码
def print_str():
print('哈哈')
a = 6
print(a)
# 回到test2导入并调用
from test import print_str
a = 77
print_str() #此时变量a属于test的局部名称空间,上面一行代码的a属于test2的全局名称空间,互不影响,但容易混淆!
print(a) #这里的变量a属于test2的全局名称空间
# 执行得到结果
哈哈
6
77
4、Python文件用途
-
当作Python程序直接运行,此时程序执行完毕立即回收对应的名称空间。
-
当作模块导入,被引用结束后回收名称空间
4.1、name和main
为了区分py文件上述的两种用途,Python提供了__name__变量,当py文件作为程序/脚本直接运行时,此时__name__变量被赋值为__main__;当作为模块被导入时,被赋值为模块名!如下所示:
# 在test.py文件直接执行以下代码
print(__name__)
# 执行得到结果
__main__
作为模块被导入时,此时在test2导入后test上面的print(name)会被赋值为模块名
import test
test.print_str()
# 执行得到结果
test
针对这种情况,在测试时可根据用途判断来执行不同代码
# test上的代码
if __name__ == '__main__':
print('我被当作程序来运行')
else:
print('我被当作模块来导入')
# 此时回到test2导入test发生的事情
import test
test.print_str()
# 执行得到结果
我被当作模块来导入
5、模块循环导入
首先这属于垃圾代码,它不应该出现在这个世界!
# test.py代码如下
print('i am test')
from test2 import sanxi
word = 'world'
# test2.py代码如下
print('i am test2')
from test import word
sanxi = 666
# 在test3上直接导入test
import test
# 执行得到结果
Traceback (most recent call last):
File "C:/Users/AERO/PycharmProjects/pythonProject1/test3.py", line 1, in <module>
import test
File "C:\Users\AERO\PycharmProjects\pythonProject1\test.py", line 2, in <module>
from test2 import sanxi
File "C:\Users\AERO\PycharmProjects\pythonProject1\test2.py", line 2, in <module>
from test import word
ImportError: cannot import name 'word' from partially initialized module 'test' (most likely due to a circular import)
i am test
i am test2
5.1、循环分析:
当test3导入test时,先打印test的i am test,接着发现需要导入test2,然后test开始导入test2!此时因为import还没跑完所以下面的变量定义还没机会执行:导入test2模块先开始打印i am test2,然后发现还得去导入一下test,于是test2去导test!这时候test已经被test3导入过一次了且还没运行完毕不会回收名称空间,因此不会再产生新的名称空间,于是test2直接跟test要word这个名称,但是因为循环导入,导致变量名这段代码没有机会被运行,所以报错无法导入名称word,反过来test跟test2要sanxi也是一个道理!
解决方案?你都不应该写出这种垃圾代码!把变量名定义放到导入代码前面就行!
5、模块查找优先级
上面我们已经讲述了关于Python模块的导入以及引用,但是大家有没有想过import是靠什么来定位模块的?或者说它怎么知道模块放在哪里?
其实Python有一个变量专门用来存放模块的位置,导入模块也是去变量里面所对应的位置依次查找!一起来看看:
如果在交互式窗口直接导入test,毫无疑问会报错没有这个模块!
>>> import test_5.py
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'test_5.py'
我们先打印下变量里面所存放的目录位置,再添加test.py位置进去
>>> import sys
>>> sys.path
['', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\python38.zip', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\DLLs', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\lib', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\site-packages']
# 现在把test.py的位置追加进去即可
>>> import sys
>>> sys.path.append(r'C:\Users\AERO\PycharmProjects\pythonProject1')
>>> sys.path
['', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\python38.zip', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\DLLs', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\lib', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32', 'C:\\Users\\AERO\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\site-packages', 'C:\\Users\\AERO\\PycharmProjects\\pythonProject1'] #test_5所在路径已成功添加
>>> import test_5
i am test #导入成功!
6、包
6.1、何为包?
随着代码量的增加,将所有代码都存放在一个Python文件中显然不是一条持续发展的道路!我们可以将代码拆分成多个.py文件统一放在一个目录中,用__init__.py文件进行组织它们,这样的目录我们称之为包!
6.2、为何用包?
包是模块的一种形式,所以也是用来当做模块导入使用!这样对于代码的维护性得到极大提高,代码逻辑也更为清晰!
6.3、如何用包?
因为包的特点是包含有__init__.py这个文件(Python3.X以后改了,没有也行,但是2.X版本必须要有!),因此导入包时,实际上只会导入__init_这个文件,所以我们需要在里面指定代码导入所有模块名称!
6.3.1、绝对导入
以包的文件夹作为起始进行导入,绝对路径导入永不出错!就是麻烦了点!
ubuntu@VM-0-8-ubuntu:~/package$ tree ../package/
../package/
├── __init__.py
├── modul1.py
├── modul4.py
└── modul.py
# 我在__init__里面导入子模块
from package import modul
6.3.2、相对导入
仅限于包内使用, 包内模块间导入时推荐使用
.:点号代表当前目录
..:双点号代表上一级目录
from . import modul