【Python基础】day15——模块、包N种调用方式详解

    技术2022-07-10  125

    模块(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()  #测试代码

     

    Processed: 0.014, SQL: 9