今天我们要讲的内容是关于模块,那我们就得先了解模块的介绍
一.模块的介绍
1、什么是模块
模块就是一系列功能的集合体
模块大致分为四种类别:
1、一个py文件就是一个模块,文件名叫test.py,模块名叫test
2、一个包含有_init_.py文件的文件夹称之为包,包也是模块
3、使用C编写并链接到python解释器的内置模块
4、已被编译为共享库或DLL的C或C++扩展
模块有三种来源:
1、自带的模块
2、第三方模块:pip3 install requests
3、自定义的模块
2、为何要用模块
1、(自带的模块,第三方模块)-> 拿来主义,提升开发效率
2、自定义模块->是为了解决代码冗余问题
3、如何用
模块都是用来被导入使用的,而不是直接运行
上面我们介绍了模块即是一个功能的集合体,那么接下来我们该怎么使用模块?
二.模块的使用方法
模块有俩种使用方法
即 import ...
和 from ... import ...
先介绍import 直接导入的方法
import ...
功能:将模块导入到本文件中,可以让本文件使用模块中的变量
语法: import xxx
- xxx 为一个模块 , 即以 .py结尾的文件
没有返回值
来个案例:
spam.py 模块文件中的内容
print('spam.py is running')
money = 1000
def read1():
print('read1')
def read2():
read1()
print('read2')
def change():
global money
money = 0
执行文件 run.py
# 1.先执行了spam文件,创建了一个spam的名称空间
# 2.自己这的名称空间内的一个名称(模块的名字)绑定了一个名称空间,绑定的名称空间是spam.py的
import spam
print(spam.money) # 即可以通过模块名加"."来取模块名内的名称空间的名字,和对应的值
spam.read1()
spam.read2()
money = 2000 # 这里的money是run.py自己的名称空间中的名称
spam.change() # 这里改变的是spam名称空间里的money名称的值
print(money) # 2000
print(spam.money) # 0
通过这个案例,我们可以总结出俩点
1、运行spam.py,创建一个模块的名称空间,将spam.py运行过程中产生的名字都丢到模块的名称空间中
2、在当前名称空间中得到一个名字,该名字是指向模块的名称空间
注意:后续导入相同的模块,直接引用首次导入成功后的名称空间,不会重复执行spam.py、不会重复创建名称空间
from ... import ... 的 使用方法
功能: from 语句让你从模块中导入一个指定的部分到当前命名空间中。
语法: from ... import ...
- 第一个... 为模块名
- 第二个...为模块下的名称变量
拿上面的案例试试:
模块文件内的代码不变
执行函数 run.py的代码改为:
# 首次导入模块发生的事情
# 1、运行spam.py,创建一个模块的名称空间,将spam.py运行过程中产生的名字都丢到模块的名称空间中
# 2、在当前名称空间中得到一个名字money,该名字是指向模块的名称空间的那个money
from spam import money
from spam import money
from spam import money
from spam import read1
# ps: 后续的导入直接引用首次导入的成功,不会重复执行spam.py、不会重复创建名称空间
# 注意下方的money的变化,要自己知道,值是怎么来的
print(money) # 1000
money = 2000
print(money) # 2000
money = 2000
read1() # 1000
money = 2000
def read1():
print('xxx', money)
read1() # xxx 2000
# 同时导入多个名称变量
from spam import money,read1,read2,change
# 同时导入多个名称变量 然后都重命名
from spam import money as m,read1 as r1,read2 as r2,change as c
print(m)
print(r1)
print(r2)
# 同时导入多个文件可以使用 * 来表示,默认的*表示导入模块中的所有名称变量
from spam import *
print(money)
print(read1)
print(read2)
print(change)
# 也可以通过 在 模块 中 使用 __all__ = ["名称变量1","名称变量2"] 来改变* 被导入时,能调用的变量名称
# 即
__all__ = ["money","read1"]
小知识
介绍完了俩种使用方法,这里有个小知识点,扩展一下
你会发现你在下面的导入的模块和名称变量 都是 打红色波浪线的.当然这并不会影响到我们导入模块后的使用
这里,教大家怎么解决这个问题,
问题原因: .通常习惯把根目录设置在最顶层文件夹 ,所以你执行的文件所属的文件夹不是一个顶级文件夹/目录.
设置方法:右击执行文件所属的文件夹--->Mark Directory as--->Sources root
这样所有的import都从这个文件夹开始
然后再介绍一下,循环导入
循环导入
概念: 两个模块互相导入,就形成了导入循环
来个案例介绍
m2.py
print('m2 的 名称 空间已 放入到内存中---->')
from m1 import f1
print('end m2')
def f2():
print('m2 的 f2 is run')
f1()
m1.py
print('m1 的 名称 空间已 放入到内存中---->')
from m2 import f2
print('end m1')
def f1():
print('m1 的 f1 is run ')
f2()
run.py
print('is run')
from m1 import f1
f1()
执行run.py的返回结果
案例的返沪结果:
W:python36python.exe Z:/线下培训/04-第四周/01-day01/课堂/02-循环导入/00案例/run.py
is run
m1 的 名称 空间已 放入到内存中---->
m2 的 名称 空间已 放入到内存中---->
Traceback (most recent call last):
File "Z:/线下培训/04-第四周/01-day01/课堂/02-循环导入/00案例/run.py", line 6, in <module>
import m1
File "Z:线下培训 4-第四周 1-day01课堂 2-循环导入 0案例m1.py", line 7, in <module>
from m2 import f2
File "Z:线下培训 4-第四周 1-day01课堂 2-循环导入 0案例m2.py", line 5, in <module>
from m1 import f1
ImportError: cannot import name 'f1' from 'm1' (Z:线下培训 4-第四周 1-day01课堂 2-循环导入 0案例m1.py)
Process finished with exit code 1
我们可以发现会抛一个异常ImportError,告诉你文件不能导入 m1模块中的f1这个功能.
why, 为撒不可以导入??
循环导入详细分析
来,我们一步步分析.
首先我们执行是文件 run.py
那我们打开run.py的代码块
print('is run') # 代码由上往下执行,先执行了该代码, 打印了 is run
import m1 # 然后我们导入了一个模块,名字为m1 这里是首次导入,即我们做了以下步骤
1. 先执行了m1文件,创建了一个m1的名称空间
然后我们打开 m1 文件内的代码
print('m1 的 名称 空间已 放入到内存中---->') # 先打印了该行内容
from m2 import f2 # 又在m1文件中导入了另外一个模块m2,又有俩个步骤
1. 先执行了m2文件,创建了一个m2的名称空间
然后我们再打开 m2 文件内的代码
print('m2 的 名称 空间已 放入到内存中---->') # 先打印了该行内容
from m1 import f1 # 又在m2 文件内导入了模块m1, 这时候,他又会执行m1文件内的内容
但是因为此时的m1文件的名称空间已经在内存中了.它不会去执行了,但是我此时要导入的是f1,但是我的内存中至始至终都还没有执行到m1 中的定义f1
函数那, 但 这个时候又导入了f1,所以这个时候会抛异常ImportError 告诉你 不能导入 f1 来自 m1的名称空间
print("end m2")
def f2(): # 在m2 文件下 定义了一个函数名称f2
print('m2 的 f2 is run')
f1()
print("end m1")
def f1(): # 在 m1 文件下 定义了 一个 函数名称 f1 ,但上面的步骤至始至终到没有执行到这
print('m1 的 f1 is run ')
f2()
2.我们获取到了在本文件中有一个名字 m1 指向了 m1 的名称空间
m1.f1() # 因为执行的文件是run.py 所以这里的 m1是本名称空间中的m1. 但是它指向的是m1的名称空间,可以使用f1这个名称
# 即这一步 是 调用了 f1 这个函数,所以我们到m1的名称空间中去执行该函数代码
def f1():
print('m1 的 f1 is run ')
f2()
注意,from m1 import f1
语句在执行的时候,会先导入 m1模块, 再引用f1,这一点很重要。
解决方案1:将导入模块放到函数名变量定义之后,即定义完f1 之后,即可,m2中的f2也是,因此现在的代码变为
run.py
run.py
print('is run')
from m1 import f1
f1()
m1.py
print('m1 的 名称 空间已 放入到内存中---->')
print('end m1')
def f1():
print('m1 的 f1 is run ')
f2()
from m2 import f2
m2.py
print('m2 的 名称 空间已 放入到内存中---->')
print('end m2')
def f2():
print('m2 的 f2 is run')
f1()
from m1 import f1
解决方案2:将导入语句放在函数定义中也是一种方案,但是函数必须要在变量定义之后执行,否则也是会报错的。但只支持变量是普通变量,而不是函数
import 语句剖析(摘抄)
要弄明白这个原因,需要捋一捋 Python 解释器处理 import 语句的流程:
-
首先解释器会检查 sys.modules(sys.modules 是一个字典,用于缓存之前导入的模块):
- 如果在 sys.modules 内找到了要导入的模块,那么直接将模块名添加到当前的名称空间,完成导入
- 如果没有找到,那么继续下面的流程
-
调用 Python 的导入协议——查找器和加载器
-
搜索 sys.meta_path,sys.mata_path 是包含元路径查找器对象的列表。Python 内有多种查找器:
- 知道如何导入内置模块
- 知道如何导入冻结模块
- 知道如何导入来自 import path 的模块
这些查找器会被按顺序查询以确定它们是否知道如何处理该名称的模块:
- 如果元路径查找器知道如何处理指定名称的模块,它将返回一个说明对象(内含加载器)
- 如果不能处理,则会返回 None
如果 sys.meta_path 处理过程到达列表末尾仍未返回说明对象,则引发 ModuleNotFoundError。任何其他被引发异常将向上传播,并放弃导入过程。
当一个模块说明被找到时,导入机制将在加载模块时使用它(及其所含的加载器)。
在模块被加载前,模块已经存在于 sys.modules 中,这可以防止无限递归或多次加载。
在模块创建完成但是还未执行之前,导入机制会设置导入相关模块属性(name ,__loader__等)
-
模块加载器提供关键的加载功能——模块执行
- 加载器会在模块的全局名称空间(module.dict)中执行模块的代码,并填充模块的名称空间
- 如果加载器无法执行执行模块,它将引发 ImportError,模块也会从 sys.modules 中移除
-
将模块名导入到当前名称空间中,完成导入
文件的搜索路径的优先级
当一个文件被执行时,它的文件搜索路径的优先级为:
1.现在内存中找
2.然后再在内置中找
3.最后在sys.path 中从左往右依次匹配.
所以我们得先介绍一下,什么是sys.path:
sys.path即是环境变量,它的环境变量是对执行文件的
它存放的是一些绝对路径的前缀.是一个列表.
即本电脑实例:
在pycharm中
import sys
print(sys.path)
当在pycharm中,打印sys.path 时 返回如下
['Z:\线下培训\04-第四周\01-day01\课堂\03-模块导入路径优先级', 'Z:\线下培训',
'Z:\线下培训\04-第四周\01-day01\课堂\01-模块的使用\02-模块的使用2',
'Z:\线下培训\04-第四周\01-day01\课堂\01-模块的使用\01-模块的使用1',
'W:\pycharm\PyCharm Professional Edition with Anaconda plugin 2020.2\plugins\python\helpers\pycharm_display',
'W:\python36\python37.zip', 'W:\python36\DLLs', 'W:\python36\lib', 'W:\python36',
'C:\Users\菩萨\AppData\Roaming\Python\Python37\site-packages', 'W:\python36\lib\site-packages',
'W:\python36\lib\site-packages\win32', 'W:\python36\lib\site-packages\win32\lib',
'W:\python36\lib\site-packages\Pythonwin',
'W:\pycharm\PyCharm Professional Edition with Anaconda plugin 2020.2\plugins\python\helpers\pycharm_matplotlib_backend']
当在一个交互式的cmd中实验如下
python3 "Z:线下培训 4-第四周 1-day01课堂 3-模块导入路径优先级 1-文件的搜索路径及优先级.py"
返回结果为:
['Z:\线下培训\04-第四周\01-day01\课堂\03-模块导入路径优先级',
'w:\python36\python37.zip', 'w:\python36\DLLs', 'w:\python36\lib', 'w:\python36',
'C:\Users\菩萨\AppData\Roaming\Python\Python37\site-packages', 'w:\python36\lib\site-packages',
'w:\python36\lib\site-packages\win32', 'w:\python36\lib\site-packages\win32\lib',
'w:\python36\lib\site-packages\Pythonwin']
我们以交互式下的环境变量为主,即 这个列表中的第一个路径应该是'Z:线下培训 4-第四周 1-day01课堂 3-模块导入路径优先级',即是当前执行文件
直属的第一个文件夹/目录,后面的为安装python解释器时一同下载到本地的文件(模块,也就是包)的路径.C:Users菩萨AppDataRoamingPythonPython37site-packages
为下载完的第三方模块存放的位置.
即实际案例集:
当被导入模块a.py和当前执行文件在同一个文件夹下,我们可以直接通过from a import f1 取到f1名称的值
from a import f1
f1() # 你好a.f1
当被导入模块b.py在当前执行文件的所属文件夹下的子文件下,导入方法如下
from bb.b import f1
f1() # 你好b.f1
当被导入模块c.py不在当前执行文件的文件夹下,即在另外一个目录下,导入方法如下
import sys
sys.path.append(r'Z:线下培训 4-第四周 1-day01课堂') # 可以向环境变量中添加被导入模块的直属文件夹
from c import f1 # 拼接成 Z:线下培训 4-第四周 1-day01课堂c.py 即可 导入 下面的 f1
f1() # 你好c.f1
所以我们总结一下,可以发现,我们sys.path这个环境变量这个列表中的元素是被导入模块的绝对的路径的前缀,和from 后面的模块名或者是import后面的模块名拼接起来,就是被导入模块的绝对路径
注意在from和 import这些关键字后面的路径分隔符以"."来表示
项目的目录规范
这是一个简单的项目目录规范
功能是一个ATM机
目录结构为:
ATM(项目根目录):
- conf(配置模块)
- settings.py
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
LOG_PATH = r'%slogaccess.log' % BASE_DIR
FUNC_DIC = {}
- core(主逻辑模块)
- src.py
from lib import common
@common.make_dic(1, "提现") # @inner -> inner(withdraw)
def withdraw():
print('提现功能。。。')
@common.make_dic(2, "转账")
def transfer():
print("转账功能")
common.logger('jkey给ly转了一个亿')
@common.make_dic(3, "查询余额")
def check_balance():
print('查询余额')
def run():
while True:
for k, v in common.settings.FUNC_DIC.items():
print(k, v[0])
choice = input("请输入命令编号,输入q退出: ").strip()
if choice == "q":
break
if choice in common.settings.FUNC_DIC:
common.settings.FUNC_DIC[choice][1]()
else:
print("输入错误,请重新输入")
- db(数据模块)
- lib(公共的库模块)
- common.py
import time
from conf import settings
def logger(msg):
with open(r'%s' % settings.LOG_PATH, mode='at', encoding='utf-8') as f:
f.write("%s %s
" % (time.strftime("%Y-%m-%d %H:%M:%S"), msg))
def make_dic(n, msg):
def inner(func):
# 添加记录
settings.FUNC_DIC[str(n)] = [msg, func]
return inner
- log(日志模块)
- access.log
- Readme(说明书)
- start.py(主执行文件)
# import sys
# import os
# # sys.path.append(r'D:python17day14代码ATM')
# BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# sys.path.append(BASE_DIR)
from core import src
src.run()
os.path.dirname(_file_)获取的是当前文件的直属文件夹
总结:
1.俩种导入模块的方式
1.1 import ...
直接导入模块,首次执行到import时,有俩个步骤.
1.1.1步骤1:执行模块内的代码,将模块内所有产生的名字都丢到模块的名称空间内
1.1.2步骤2:在执行文件中生成一个名称为模块名,指向的是模块的名称空间
注意:只有首次执行时,才会产生上面的俩个步骤,后续导入直接在内存中找对应的名称空间
1.2 from ... import ...
和 import都为导入模块的方式.
import 导入模块,每次使用模块中的函数都要是定是哪个模块。
from…import 导入模块,每次使用模块中的函数,直接使用函数就可以了;注因为已经知道该函数是那个模块中的了。
2.循环导入
对于模块导入循环,最佳的解决方案还是尽量避免两个模块互相导入。如果互相导入不可避免,那需要将导入语句放在变量定义之后。或者 将 from ... import ...
语句放在函数中定义也是一种方案,但是函数必须要在变量定义之后执行,否则也是会报错的。
3.模块导入的优先级
先是内存中,然后再到内置中,最后才到我们的sys.path这个环境变量中
注意这个sys.path是相对于执行文件的,它的第一个值是执行文件的直属文件夹的绝对路径.
4.项目的简单目录结构
这是一个简单的项目目录规范
功能是一个ATM机
目录结构为:
ATM(项目根目录):
- conf(配置模块)
- settings.py
- core(主逻辑模块)
- src.py
- db(数据模块)
- lib(公共的库模块)
- log(日志模块)
- access.log
- Readme(说明书)
- start.py(主执行文件)