模块(module)

概念:

  • 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
  • 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式,在python中,一个py文件就称之为一个模块

模块有什么好处?

  • 最大的好处就是大大提高了代码的可维护性
  • 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块。包括python的内置模块和来自第三方的模块

模块的种类

  • python标准库
  • 第三方模块
  • 应用程序自定义模块

另外,使用模块还可以避免函数名和变量名冲突,相同名称的函数和变量完全可以分别存在不同的模块中 ,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。

第三方模块调用应用示例:

import cal

print(cal.add(5,6))

执行结果:

11

Process finished with exit code 0

程序执行效果:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_包package

从上面的执行结果中可以看出,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文件并执行这个文件:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_模块module_02

调用第三方模块中的变量应用示例:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_ide_03

这里在调用第三方模块中的变量的时候,需要在这个变量的前面加上模块名,也就是类似上面的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中创建包的方法:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_ide_04

python package其实也是一个文件夹,不过这个文件夹在创建的同时,会自动创建一个init文件,这个init文件就是区分文件夹和包的方法。

首先我们在当前目录下创建一个包web,然后在与包同级的目录下面创建bin.py程序,在该程序中调用web包中的模块的方法:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_Python_05

如果我们的包是递归的情况,也就是在web包里面有一个包test,test包中有模块logger,那么与web同级的程序该如何调用logger模块呢?

首先看一下目录结构

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_模块module_06

在看一下程序执行详情:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_python_07

也就是说,从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

程序执行效果:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_模块module_08

调用包的方法:

import 包名    #实际上是执行了包下面的__init__.py文件

执行效果:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_python_09

 

重要的BASEDIR

下面我们来看一个案例:

1.首先我们程序的目录结构是这样的

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_python_10


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__内置变量的程序:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_python_11

结果很显然是相对路径,也就验证了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

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_ide_12


到此,我们已经可以去到,当前程序的相对路径。

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__内置变量解决这个问题。示例:

一个模块的方法如何被另外一个模块调用JAVA中 一个模块被n个模块调用_包package_13

核心就是在功能函数中,将测试测试代码放在__name__内置变量里面:

def log():
    print("ok")

if __name__ == '__main__':
    log()  #测试代码