模块(module)
概念:
- 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
- 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式,在python中,一个py文件就称之为一个模块
模块有什么好处?
- 最大的好处就是大大提高了代码的可维护性
- 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块。包括python的内置模块和来自第三方的模块
模块的种类
- python标准库
- 第三方模块
- 应用程序自定义模块
另外,使用模块还可以避免函数名和变量名冲突,相同名称的函数和变量完全可以分别存在不同的模块中 ,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。
第三方模块调用应用示例:
import cal
print(cal.add(5,6))
执行结果:
11
Process finished with exit code 0
程序执行效果:
从上面的执行结果中可以看出,python中确实没有cal这个模块,并且在pycharm中已经飘红了,说明是程序报错,但是在执行程序的时候,发现是没有任何报错的,说明这个程序是没有问题的,也就是这个模块的调用是没有任何问题的,只不过python的IDE——pycharm没有自动识别到这个模块
下面来查看第三方模块的搜索路径:
import sys
import cal #解释器通过搜索路径找到cal.py文件后,解释这个py文件,也就是执行这个py文件
print(cal.add(5,6))
print(sys.path)
执行结果:
11
['H:\\python_scripts\\study_scripts\\daily\\day19\\moudle_dir', 'H:\\python_scripts', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_display', 'E:\\python_ide\\venv\\Scripts\\python36.zip', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36', 'E:\\python_ide\\venv', 'E:\\python_ide\\venv\\lib\\site-packages', 'E:\\python_ide\\venv\\lib\\site-packages\\setuptools-40.8.0-py3.6.egg', 'E:\\python_ide\\venv\\lib\\site-packages\\pip-19.0.3-py3.6.egg', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_matplotlib_backend']
Process finished with exit code 0
取出sys.path的路径分析:
['H:\\python_scripts\\study_scripts\\daily\\day19\\moudle_dir', 'H:\\python_scripts', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_display', 'E:\\python_ide\\venv\\Scripts\\python36.zip', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36', 'E:\\python_ide\\venv', 'E:\\python_ide\\venv\\lib\\site-packages', 'E:\\python_ide\\venv\\lib\\site-packages\\setuptools-40.8.0-py3.6.egg', 'E:\\python_ide\\venv\\lib\\site-packages\\pip-19.0.3-py3.6.egg', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_matplotlib_backend']
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
'H:\\python_scripts\\study_scripts\\daily\\day19\\moudle_dir', #这是第一个路径,第一个路径是当前目录,说明程序在调用模块的时候,首先会去当前目录找。如果能找到就直接调用
'H:\\python_scripts', #上级目录
'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_display', #后面就是一些python标准库文件的路径,也就是标准库模块对应的py文件的存储路径,执行程序的时候,逐级寻找
'E:\\python_ide\\venv\\Scripts\\python36.zip',
'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\DLLs',
'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib',
'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36',
'E:\\python_ide\\venv',
'E:\\python_ide\\venv\\lib\\site-packages',
'E:\\python_ide\\venv\\lib\\site-packages\\setuptools-40.8.0-py3.6.egg',
'E:\\python_ide\\venv\\lib\\site-packages\\pip-19.0.3-py3.6.egg',
'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_matplotlib_backend'
验证调用模块的时候,也就是程序执行到import cal后,是找到cal.py文件并执行这个文件:
调用第三方模块中的变量应用示例:
这里在调用第三方模块中的变量的时候,需要在这个变量的前面加上模块名,也就是类似上面的cal.x
因为在上面的程序中,可以看到,调用模块的时候,解释器会将模块中所有的函数全部加载一遍,也就相当于全部执行一遍,对于类似time模块这种,一个模块中仅有少部分函数的模块来说,加载速度还可以接受,但是对于函数很多的模块,如果我们仅仅是使用该模块其中的一个函数的话,加载全部的模块效率是非常低的,所以,对于这种情况,我们采用的方法是from cal import add
from cal import add,sub #从模块里调用方法,这种调用方式,python解释器仅仅加载的只是这个模块中的这一个方法
print(add(5,6)) #注意这里在使用模块的时候跟直接import cal是不同的
执行结果:
11
-1
Process finished with exit code 0
从上面可以看出,from cal import add,sub这个方式调用模块,可以不需要写模块名,直接使用模块中的函数,(也就是变cal.add()为add())那么如果需要将模块中所有的函数全部使用from这种方式加载的方法是:
from cal import *
print(add(5,6))
print(sub(5,6))
执行结果:
11
-1
Process finished with exit code 0
但是这种方法有一种非常严重的问题就是,如果我们在程序中使用from的方式引用,那么程序中与模块中同名的函数就会冲突,这种情况下,虽然程序不会报错,但是默认使用的是程序中自定义的函数:
from cal import *
def add(x,y):
return x+y+2
print(add(5,6))
print(sub(5,6))
模块中的代码:
x=123
def add(x,y):
return x+y
def sub(x,y):
return x-y
执行结果:
13
-1
Process finished with exit code 0
如果import加载模块的语句在程序自定义变量的下面的话,则会优点调用模块中的函数,也就是以程序的执行顺序为主。
解决模块中函数与程序中函数重名的方法:
from cal import add as plus
print(plus(5,6))
执行结果:
11
Process finished with exit code 0
包package
如果不同的人编写的模块名相同怎么办?
- 为了避免模块名冲突python又引入了按目录来组织模块的方法,称为包
pycharm中创建包的方法:
python package其实也是一个文件夹,不过这个文件夹在创建的同时,会自动创建一个init文件,这个init文件就是区分文件夹和包的方法。
首先我们在当前目录下创建一个包web,然后在与包同级的目录下面创建bin.py程序,在该程序中调用web包中的模块的方法:
如果我们的包是递归的情况,也就是在web包里面有一个包test,test包中有模块logger,那么与web同级的程序该如何调用logger模块呢?
首先看一下目录结构
在看一下程序执行详情:
也就是说,从package中调用模块的方法是:
from 目录(层级) import 模块
如:
from web import logger
from web.test import logger
此时执行程序所在的目录应该是from后面的最上级目录
如果需要调用包内的某一个方法:
from web.test.logger import pt
pt()
执行结果:
E:\python_ide\venv\Scripts\python.exe H:/python_scripts/study_scripts/daily/day19/bin.py
ok
Process finished with exit code 0
程序执行效果:
调用包的方法:
import 包名 #实际上是执行了包下面的__init__.py文件
执行效果:
重要的BASEDIR
下面我们来看一个案例:
1.首先我们程序的目录结构是这样的
2.执行下面的程序
# from module_all import logger
# from day20.module_all import logger
# from daily.day20.module_all import logger #在导入模块的时候,以上三种方式都会报错
from study_scripts.daily.day20.module_all import logger #只有这种方式是正确的
logger.log()
报错内容:
Traceback (most recent call last):
File "H:/python_scripts/study_scripts/daily/day20/bin/bin.py", line 1, in <module>
from module_all import logger
ModuleNotFoundError: No module named 'module_all'
Process finished with exit code 1
报错的原因是程序找不到“module这个模块
那么第四中方式加载模块发现是成功的原因是
# from module_all import logger
# # from day20.module_all import logger
# # from daily.day20.module_all import logger
# # from study_scripts.daily.day20.module_all import logger
#
# logger.log()
import sys #我们把上面的程序部分引掉,打印当前的环境变量
print(sys.path)
打印python环境变量的执行结果:
['H:\\python_scripts\\study_scripts\\daily\\day20\\bin', 'H:\\python_scripts', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_display', 'E:\\python_ide\\venv\\Scripts\\python36.zip', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36', 'E:\\python_ide\\venv', 'E:\\python_ide\\venv\\lib\\site-packages', 'E:\\python_ide\\venv\\lib\\site-packages\\setuptools-40.8.0-py3.6.egg', 'E:\\python_ide\\venv\\lib\\site-packages\\pip-19.0.3-py3.6.egg', 'D:\\Program Files\\JetBrains\\PyCharm 2019.2.4\\helpers\\pycharm_matplotlib_backend']
Process finished with exit code 0
从执行结果中,我们可以看到
第一个目录是:
'H:\\python_scripts\\study_scripts\\daily\\day20\\bin' #这个是执行程序所在的目录名
但是需要调用的模块所在的包并没有在这个目录下面,也就意味着,程序在这个目录中找不到这个包,就去找第二个目录
第二个目录是:
'H:\\python_scripts' #这个目录是最上层目录
那么程序执行的时候,在程序自身所在的目录下面没有找打这个包,就会去这个第二个目录中找,而第二个目录中并没有这个包,但是可以从第二个目录的一系列子目录中找打这个包,所以第四种方法我们执行成功了。
也就是调用方式为:
from study_scripts.daily.day20.module_all import logger
但是第一个目录、第二个目录都是pycharm给我们添加的路径,也就是说,如果我们的程序不是在pycharm中执行,例如在cmd命令行终端上执行的话,就会报错,提示仍然是找不到包
C:\Users\yuyan>python
Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.path)
['', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\python36.zip', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36', 'C:\\Users\\yuyan\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site-packages']
>>>
从上面命令行终端的执行结果中,可以看到并没有pycharm给我们添加的第一个目录和第二个目录,所以我们在命令行中执行这个程序的时候,一定是找不到这个包和对应模块的
那么解决方式有多种:
第一、我们的程序使用sys.path.append()自行将上面的需要的目录添加到sys.path中就可以找到这个包了。(当然添加目录的代码应该是调用模块之前执行)。但是这种方式有一种问题是,一旦程序移动目录,或者发送给其他人使用的话,程序会由于绝对路径的问题,再次出现找不到包的情况。
第二、由程序自动获取所在路径
内置变量__file__:
print(__file__)
执行结果:
H:/python_scripts/study_scripts/daily/day20/bin/bin.py #这个路径就是当前程序所在的绝对路径
Process finished with exit code 0
这种出现绝对路径的情况,也是只是在pycharm中才会出现的,也就是说,pycharm将我们的相对路径变成了绝对路径。
比如我们在命令行终端中执行这个打印__file__内置变量的程序:
结果很显然是相对路径,也就验证了pycharm帮我们将相对路径转换为了绝对路径的情况。所以使用__file__内置变量不能帮我们解决上面的问题。
那么我们使用os模块中的os.path.abspath()方法就可以将当前的相对路径手动转化为绝对路径,程序示例:
import os
print(os.path.abspath(__file__))
执行结果:
H:\python_scripts\study_scripts\daily\day20\bin\bin.py
Process finished with exit code 0
到此,我们已经可以去到,当前程序的相对路径。
import os
print(os.path.abspath(__file__))
print(os.path.dirname(os.path.abspath(__file__)))
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
执行结果:
H:\python_scripts\study_scripts\daily\day20\bin\bin.py
H:\python_scripts\study_scripts\daily\day20\bin
H:\python_scripts\study_scripts\daily\day20
Process finished with exit code 0
通过上面的os模块中的方法,我们拿到的是当前程序的父目录,需要使用的包是在day20这个目录,那么我们再取一下当前目录的父目录就可以了。最后将这个目录,通过sys.path.append()方法添加到sys.path中就可以了
__name__内置变量的使用:
我们在测试代码的时候,对于功能函数(包中的第三方模块),一般会执行函数以验证代码的可行性,但是如果在测试完成之后,没有删除测试代码的话,会导致功能函数调用的时候,误将测试代码也执行了一遍,所以这里可以使用__name__内置变量解决这个问题。示例:
核心就是在功能函数中,将测试测试代码放在__name__内置变量里面:
def log():
print("ok")
if __name__ == '__main__':
log() #测试代码