21、Python之模块基础

Python模块 / 2020-12-08

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文件首次导入模块会发生的事情(后续引用首次):

  1. 执行模块中代码
  2. 将执行过程中产生的名称都存放于该模块的名称空间中
  3. 在导入模块的当前文件中产生一个模块名称(如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
世间微尘里 独爱茶酒中