1.模块介绍
1.1 模块的概念
模块是什么 ?
模块就是一系列功能(函数)的集合体
模块的分类
- 内置模块 c语言写的模块
- 第三方模块 别人写好的模块
- 自定义模块 自己自定义的
一个python文件本身就是一个模块 , 文件名m.py , 模块名叫m
ps:模块分为4中形式
1.使用python编写的.py文件
2.已被编译为共享库或DLL的C或C++扩展
3.把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包
4.使用C编写并链接到python解释器的内置模块
1.2 为什么要用模块
- 内置与第三方的模块拿来就用,无需定义,这种拿来主义,可以极大的提升自己的开发效率
- 可以将程序的各部分功能提取出来放到一模块中为大家共享使用
- 减少了代码冗余,程序组织结构更加清晰
2. 模块的两种导入方式
2.1 import
[创建模块]
# m.py
print('======m=====')
x = 100
def get():
print("get ========%s" % x)
def change():
global x
x += 1
return x
[导入模块]
# demo.py
import m # 导入m模块
x = 1
y = 2
# ======m=====
# 打印l ======m===== ,说明导入模块,会运行模块里面的代码
[思考] 首次模块会发生什么事情
1.产生m.py的名称空间
2.执行m.py代码,将m.py运行过程中产生的名字都丢到m的名称空间
3.在当前文件中产生一个名字m,指向1中产生的名称空间
[之后导入]
之后的导入,都是直接引用第一次导入产生的m.py名称空间,不会重复执行代码
[深入理解]
# demo.py
import m
print(m.x) # 100
print(m.get) # <function get at 0x000001FD80567620>
print(m.change) # <function change at 0x000001FD81616510>
[强调]
- 模块名.名字,是指名道姓的问某一个模块要名字对应的值,不会与当前名称空间的名字发生冲突
- 无论是查看还是修改操作的都是以定义阶段为准(原模块本身),与调用位置无关
# demo.py
import m
x = 333
m.get() # get ======== 100
m.change() # 改变的是m名称空间里面的x值
print(x) # 333
print(m.x) # 101
[补充]
# 1.可以以逗号为分隔符在一行导入多个模块
# 建议如下所示导入多个模块,层次清晰
import time
import random
import m
# 不建议在一行同时导入多个模块
import time, random, m
# 2.导入模块的规范
# 1.python内置模块
# 2.第三方模块
# 3.程序员自定义模块
import time
import sys
import 第三方模块1
import 第三方模块2
import 自定义模块1
import 自定义模块1
# 3.import ... as ...
# 为导入的模块起一个别名,以后可以通过别名.名字,取到模块里面的名字
# 适用于比较长的第三方模块
import jhcsdfveubvf as f
# 4.模块是第一类对象,和变量名,函数名一样,可以被赋值,可以当参数,返回值,容器的元素
# 5.自定义模块的命名应该采用纯小写+下划线风格,py2中还是驼峰体
# 6.可以在函数中导入模块
2.2 from ... import ....
# foo.py
print('======foo=====')
x = 0
def get():
print("get ========%s" % x)
def change():
global x
x += 1
return x
# run.py
from foo import X # x=模块foo中值0的内存地址
from foo import get
from foo import change
print(x) # 0
print(get) # <function get at 0x0000029FBD8E7620>
print(change) # <function change at 0x0000029FBE996510>
# 注意:x,get,change这三个名字是在run的名称空间中,但是指向的都是foo里面值的内存地址,并不是名字
# 只是他们的名字相同又指向相同的内存地址,但是不在一个名称空间,所以不会相互影响
[导入模块发生的事]
# 1、产一个模块的名称空间
# 2、运行foo.py将运行过程中产生的名字都丢到模块的名称空间去
# 3、在当前名称空间拿到一个名字,该名字指向模块名称空间中的某一个内存地址
[强调]
# run.py
from foo import X # x=模块foo中值0的内存地址
from foo import get
from foo import change
print(x) # 0
print(get) # <function get at 0x0000029FBD8E7620>
print(change) # <function change at 0x0000029FBE996510>
x = 3333 # 改变全局名称空间中x的指向,现在x指向值3333的内存地址
print(x) # 3333
get() # 仍然打印x=0,因为get中的x是引用foo名称空间中x指向的值,foo中x的指向并没有变
change() # 改变foo中x的内存地址的指向
get() # 打印1
print(x) # 虽然foo中x的指向发送改变了,但是run中的这个x并不会发生变化,还是原来的内存地址0
from foo import x # 这个时候x就指向,foo里面x指向的新地址了
print(x) # 打印 1
[补充]
#一行导入多个名字(不推荐)
from foo import X, get,change
# *:导入模块中的所有名字,当用到一个模块中超级多的名字时,可以用*,但是最好也要规避掉重名的问题
from foo import *|
print(x)
# 起别名
from foo import get as g # 针对get起了一个别名g
[了解]
# 了解 : __all__ = ["名字1","名字2",.....],列表的元素全是字符串格式的名字
# 导入*,实际上就是把列表中的名字全导入进入了,可以通过__all__控制*代表的名字有哪些
2.3 两种方式的比较
impot导入模块在使用时必须加前缀"模块."
优点:肯定不会与当前名称空间中的名字冲突
缺点:加前缀显得麻烦
from.. . impat.. .导入模块在使用时不用加前缀
优点:代码更精简
缺点:容易与当前名称空间混淆
3. 循环导入
极力不推荐在程序中设计有模块的循环导入,但是如果真的遇到,记住两种解决方式
m1.py
print("正在导入m1")
from m2 import y
x = 1
m2.py
print("正在导入m2")
from m1 import x
y = 2
run.py
import m1
# 运行代码,首先会加载m1的名称空间,然后运行里面的代码,当运行到第二句的时候,此时m1的名称空间中还没有x这个名字,由于是首次导入会加载m2的名称空间,运行m2的代码,所以控制台会打印正在导入m1,正在导入m2,当运行当m2的第二句代码时,由于m1不是首次导入,所以不会再加载名称空间,也不会执行代码,但是m2问m1要x这个名字,由于m1中的x这个名字还没有加载到名称空间,所以程序报错
[解决方法]
# 方式1:
# 将m1,和m2中的from m2 import y,from m1 import x,放在文件的最后一行,确保导入的时候,名字已经完全加载到名称空间了
# 方式2:
# 将模块导入的代码,放在函数体中,这样保证在运行代码的时候,函数未调用函数体里面的代码不执行
4. 搜索模块路径的优先级
无论是 import还是from ...import 在导入模块时都涉及到查找问题
[优先级]
- 内存 (内置模块,默认会自动加载到内存中)
- 硬盘 : 按照
sys.path(环境变量)
中存放的文件的顺序依次查找要导入的模块
import sys
print(sys.path)
['E:\project\python\s29code\day21', 'E:\project\python\s29code', 'E:\developTools\pycharm\PyCharm 2020.1\plugins\python\helpers\pycharm_display', 'E:\developTools\Anaconda3\python36.zip', 'E:\developTools\Anaconda3\DLLs', 'E:\developTools\Anaconda3\lib', 'E:\developTools\Anaconda3', 'E:\developTools\Anaconda3\lib\site-packages', 'E:\developTools\Anaconda3\lib\site-packages\win32', 'E:\developTools\Anaconda3\lib\site-packages\win32\lib', 'E:\developTools\Anaconda3\lib\site-packages\Pythonwin', 'E:\developTools\pycharm\PyCharm 2020.1\plugins\python\helpers\pycharm_matplotlib_backend']
# 其中第一个文件夹是当前执行文件所在的文件夹 E:\project\python\s29code\day21
# 其中第二个文件夹是当前项目的文件夹 E:\project\python\s29code
# 这个值是pycharm自动添加的,所以当他不存在,值里面的压缩包就当文件夹看待
[补充]
通过sys.modules
查看已将加载到内存的模块,内容格式 {'模块名':'内存地址'}
print('foo' in sys.modules) # 判断foo模块是否在当前内存中
print('m1' in sys.modules) # 判断foo模块是否在当前内存中
{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module 'importlib._bootstrap_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'nt': <module 'nt' (built-in)>,...}
[python的优化机制]
导入模块会发生模块名和对应的内存地址的捆绑关系,但是你通过del 模块名
是无法从内存中把那块空间回收的,因为python导入一个模块是较耗费内存的
如果你中途从内存中删除了,再导入就会再开辟内存空间,极大的消耗内存
同样你在函数内部中导入模块,函数调用结束后,导入的模块在内存中也不会回收
只有当当前运行文件结束,无其他文件对模块中的名字有引用,模块才会从内存中消失
5. 模块规范写法
我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,
因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下
"The module is used to..." # 模块的文档描述
import sys # 导入模块
x = 1 # 定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
class Foo: # 定义类,并写好类的注释
'Class Foo is used to...'
pass
def test(): # 定义函数,并写好函数的注释
'Function test is used to…'
pass
if __name__ == '__main__': # 主程序
test() # 在被当做脚本执行时,执行此处的代码
6. py文件的用途
6.1 当做模块导入
# foo.py
print('======m=====')
x = 0
def get():
print("get ========%s" % x)
def change():
global x
x += 1
return x
# run.py
import foo # 首次导入模块,执行代码
#print(foo)
print(foo.x) # 0
foo.change() # 把x指向的内存地址改变了,指向了1
print(foo.x) # 1
x = 1
y = 2
print('11111')
print('22222')
# 名称空间的回收顺序
首次导入foo模块,foo代码会运行,但是代码运行结束后它的名称空间没有被回收,因为里面的名字被run.py引用了,紧接着run.py中的代码运行完毕后,它的名称空间里面的名字没有被其他引用,运行完后,run.py的名称空间就被回收了,foo的名称空间也被回收了
# 总结: 模块没有被其他文件引用就被回收了
5.2 当做程序运行
如果一个模块在开发测试的时候,肯定会要运行,但是如果被别人导入的时候,不想执行里面的函数怎么办呢?
# foo.py
print('======m=====')
x = 100
def get():
print("get ========%s" % x)
def change():
global x
x += 1
return x
if __name__ == '__main__':
# 当作程序运行的时候做的事情
get()
change()
else:
# 被当做模块导入的时候做的事情
pass
# 每一个py文件内部都有一个__name__变量,
# 当文件当做程序运行的时候,__name__ == __main__
# 当文件当做模块导入时,__name__ == 模块名,即去掉后缀的文件名