什么是模块
模块就是一组功能的集合。(每次创建的一个python文件就是一个模块),那么函数是什么呢?函数是一个功能的实现,也就是说模块更像是一堆函数的封装。
为什么要用模块
1. 从文件级别组织程序,更方便管理
之前我们写的一个程序大都是存在一个文件中的,包含着各式各样的函数功能。为了更好的管理,我们可以把不同的功能集合统一的写到一个文件中,当我们要使用的时候,加载一下模块进行使用就可以了。例如我们之前写的购物城程序,无论是atm操作过程还是管理员的操作过程我们都是写在了一个文件中,查看以及维护起来相当的麻烦,因此我们可以创建两个文件,一个用来专门的放atm的操作,一个用来专门的放管理员的操作,这样就可以很方便的管理和维护了。
2. 提高开发效率
我们都知道对于一些常用的功能,我们一般都会把它写成一个函数以便后续不断的调用,其实模块也是这个原理,只是模块是常用的一类函数的集合,也是方便我们之后调用的。例如,我们之后遇到一个项目,发现项目中的很多功能之前已经有大神把它写成了一个模块了,这样我们就可以不用再去自己造轮子,可以直接使用这个模块,这样可以大大的提高我们的开发效率。
模块的分类
# 模块的分类 ''' 1. 自定义模块 2. 用c语言写成的连接到python解释器的内置模块 3. 包(把一系列模块组织到一起的文件夹) 4. 已被编译为共享库或DLL的C或C++扩展 '''
模块之import的使用
# import的使用 ''' 1. import 模块名 2. import 很长的一个模块名 as 小名 3. import 模块名1, 模块名2, 模块名3 '''
例一:import 模块名
当我们执行了import m1之后发生的三个步骤:
# 这是一个m2文件 import m1 # 当执行到这个import的时候会做以下几件事 ''' 1. 执行m2.py文件的时候会创建一个属于m2.py的名称空间 2. 然后去执行模块也就是m1.py里面的内容,首先打开m1.py,再创建一个m1.py的名称空间 用来存放m1.py中的变量名和内存地址的空间。 3. 将m1作为一个变量名加入到m2.py的名称空间内,指向m1.py的名称空间 '''
从这三个步骤我们就可以看出来在导入模块的时候并不是说把导入模块的名称空间照搬过来,而是在执行文件内创建了一个变量指向了模块的命名空间。
例子:创建两个文件一个spam.py 一个是test文件
#spam.py print('from the spam.py') money=1000 def read1(): print('spam模块:',money) def read2(): print('spam模块') read1() def change(): global money money=0
测试: 只要是通过import导入的模块,命名空间是很明了的,通过【spam.money】这种方式调用的用的都是模块的命名空间,而没有用模块调用的用的名称空间都是当前执行文件的。
# 定义一个变量 money = 0 # 导入spam模块,此时会执行spam,因此结果会首先打印 # from the spam.py import spam # 打印money,会去找test.py的命名空间 print(money) # 可以理解为打印spam内的money print(spam.money) # 结果: # from the spam.py # 0 # 1000
# 函数的测试 def read1(): print('这是test文件') # 导入spam模块,此时会执行spam,因此结果会首先打印 # from the spam.py import spam # 会去找test.py的命名空间并且执行函数 # 打印这是test文件 read1() # 可以理解为执行spam内的read1函数 spam.read1() # 结果: # from the spam.py # 这是test文件 # spam模块! 1000
# 导入spam模块,此时会执行spam,因此结果会首先打印 # from the spam.py import spam # 当执行read2的时候会去调用read1,此时的read1仍然是spam的命名空间的 spam.read2()
money = 100 # 导入spam模块,此时会执行spam,因此结果会首先打印 # from the spam.py import spam # 执行change的时候有一个修改全局变量的操作, # 它的名称空间用的也是spam的 spam.change() # 整个过程不会改变test命名空间的值所以打印100 print(money) # money已经在change里面得到了改变,因此打印的为0 print(spam.money) # 结果: # from the spam.py # 100 # 0
例二:import 很长的一个模块名 as 小名
# 这种方式的特点 1. 简洁 2. 可以通过别名的形式进行选择不同的操作
测试: 创建两个文件,mysql.py , oracle.py
# oracle.py文件的内容 def sqlparse(): print('from oracle sqlparse') # mysql.py文件的内容 def sqlparse(): print('from mysql sqlparse') # 用户选择是用mysql数据库还是oracle数据库 choice = input('>>:').strip() if choice == 'mysql': import mysql as db elif choice == 'oracle': import oracle as db else: print('Wrong!') # 根据用户的数据选择使用不同的数据库 db.sqlparse()
例三: import 模块名1, 模块名2, 模块名3
# 使程序更加简洁 import m1, m2, mysql
模块之from xxx import xx 的使用
使用方法:
from spam import money
from和import的比较
# 有了import为什么还要用from ''' from和import的区别在于: 1. import不会把导入模块内变量添加到执行文件的命名空间内, 2. 但是from是把导入的模块直接在自己的命名空间内添加了一份和模块内一摸一样的命名空间 from: 优点: 方便了,我们可以直接使用导入的变量而不用想import一样还要用spam.money 缺点: 容易和我们现在命名空间内的变量重复了 '''
测试:
#测试一:导入read1函数 # 首先会执行spam from spam import read1 read1() # 结果: # from the spam.py # spam模块! 1000
# 测试二:导入read2函数 # 首先会执行spam from spam import read2 def read1(): print('+++++++++') # read2内执行read1的时候和import一样命名空间还是在spam内 read2() # 结果: # from the spam.py # spam模块 # spam模块! 1000
# 测试三: 导入change函数 money = 100 from spam import change from spam import money # 因为导入的money把上面定义的money给覆盖掉了 # 因此现在money指向的是spam里面的内存地址,打印1000 print(money) # change内修改的全局变量依然是spam命名空间内的 change() # 因为change修改了money的值,但是修改的是spam里面的内存空间地址 # 但是在test里面的内存地址指向的还是原来的1000的地址,并没有发生改变, # 所以打印出来的为1000 print(money)
from的别名和多个导入:
from spam import money as m from spam import money, read1, read2
模块的循环嵌套:
模块的循环嵌套是很容易出现问题的,但是归根结底只有一点就是执行并不代表导入。
print('这个文件执行了!') # 导入自己 import test # 此处是打印了两次,因为我执行文件虽然是加入了内存,但是作为模块还是没有 # 导入到内存中的,因此当执行了import之后又执行了一遍
测试: 三个文件内容如下:
# m1.py文件 print('正在导入m1') from m2 import y x = 'm1' # m2.py文件 print('导入模块m2') from m2 import x y = 1 # test文件 import m1
当执行此文件的时候会报错,这个原因就是
错误分析:
# test文件 # 会去执行m1.py import m1 # m1.py文件 # 打印此内容 print('正在导入m1') # 去执行m2文件 from m2 import y x = 'm1' # m2.py文件 # 打印此内容 print('导入模块m2') # 因为在test中已经导入了m1因此内存中已经有了m1模块,就不会再导入了, # 但是m1中的x = 'm1'还没有执行呢,所以报错了 from m1 import x # y = 1
模块的琐碎知识点:
''' 模块的重载: 考虑到性能的原因,每个模块只被导入一次,放入字典sys.module中, 如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块。 模块的内置的全局变量__name__ 如果执行的是当前的文件__name__ = __main__ 如果只是被当做模块的形式被执行,__name__ == 模块名 注意不是别名 脚本和模块 模块就是一系列功能的集合,等待被导入之后使用 脚本就是一个程序,用来执行 模块的循环导入 文件的执行虽然也会加载到内存中,但是并不是模块加载到内存,也就是执行并不等于导入 '''
模块的搜索路径
当执行这个语句的时候,我们要去执行spam文件,但是系统是怎么知道spam文件在哪里的呢?这个涉及到了模块的搜索路径了,模块的寻找路径是这样的:
# 模块的搜索路径 ''' 1. 查找内存中是否存在此模块 2. 是否是内置函数 3. 去系统路径中找是否存在此文件,查找顺序如下 H:\python_study\day17 当前文件所在目录 H:\python_study\venv\Scripts\python36.zip E:\software\python3\DLLs E:\software\python3\lib E:\software\python3 H:\python_study\venv H:\python_study\venv\lib\site-packages H:\python_study\venv\lib\site-packages\setuptools-39.1.0-py3.6.egg H:\python_study\venv\lib\site-packages\pip-10.0.1-py3.6.egg E:\software\pycharm\PyCharm 2018.1.4\helpers\pycharm_matplotlib_backend '''
从路径可以看出来如果我们在当前目录下面重新创建一个目录写入一个模块的话,默认情况下导入的时候是找不到路径的,我们需要通过sys.path方法把它导入进去。
sys.path(r'H:\python_study\day17')
作业:
1. 定义一个cuboid模块,模块中有三个变量长(long)宽(wide)高(high),数值自定义,有一个返回值为周长的perimeter方法,一个返回值为表面积的area方法
#2.定义一个cuboid模块,模块中有三个变量长(long)宽(wide)高(high),数值自定义, # 有一个返回值为周长的perimeter方法,一个返回值为表面积的area方法 long = 2.5 wide = 3.5 high = 1 def perimeter(): return (long + wide + high) * 4 def area(): return (long * wide + long * high + wide * high) * 2
2. 定义一个用户文件stu1.py,在该文件中打印cuboid的长宽高,并获得周长和表面积,打印出来
# 方法一:import import cuboid print('长: {long} 宽: {width} 高: {high} 周长: {perimeter} 面积: {area}'.format( long=cuboid.long, width=cuboid.width, high=cuboid.high, perimeter=cuboid.perimeter(), area=cuboid.area() )) # 方法二:from from cuboid import long, width, high, perimeter, area print('长: {long} 宽: {width} 高: {high} 周长: {perimeter} 面积: {area}'.format( long=long, width=width, high=high, perimeter=perimeter(), area=area() ))
3. 在stu2.py文件中导入cuboid模块时为模块起简单别名,利用别名完成第3题中完成的操作
# 方法一:import import cuboid as cb print('长: {long} 宽: {width} 高: {high} 周长: {perimeter} 面积: {area}'.format( long=cb.long, width=cb.width, high=cb.high, perimeter=cb.perimeter(), area=cb.area() )) # 方法二:from from cuboid import long as l, width as w, high as h, perimeter as p, area as a print('长: {long} 宽: {width} 高: {high} 周长: {perimeter} 面积: {area}'.format( long=l, width=w, high=h, perimeter=p(), area=a() ))
4.比较总结import与from...import...各自的优缺点
import: 模块和源文件各自都有自己的命名空间,不会与当前执行文件中的名字产生冲突,但是使用变量的时候会麻烦一点 from: 模块的命名空间会复制某些部分到当前执行文件中,所以会与当前执行文件中的名字产生冲突,使用起来会方便一些