模块
一 什么是模块
模块就是一系列功能的集合体
Ⅰ 内置的模块
Ⅱ 第三方的模块
Ⅲ 自定义的模块
一个python文件本身就是一个模块,文件名m.py,模块名叫m
为什么要用模块
Ⅰ 内置与第三方的模块拿来就用,无需定义,这种拿来主义,可以极大的提升开发效率
Ⅱ 自定义的模块:可以将程序中的各部分功能提前出来放到一个模块中为大家共享使用,好处是减少了代码冗余,程序组织结构更加清晰
二 模块的使用
一import使用import foo
首次导入模块会发生:
1 产生foo的名称空间,将foo.py运行过程中产生的名字都丢到foo的名称空间中
2 执行foo.py
3 在当前文件中产生了foo名字,该名字指向2中产生的名称空间
4 之后的导入,都是直接引用首次导入产生的foo的名称空间,不会重复执行foo文件中的代码
import foo
# 引用:名称空间的关系以定义阶段为准
# 强调1:模块名.名字,指名道姓问某一个模块要名字对应的值,不会与当前名称空间中的名字发生冲突
# 强调2:无论是查看还是修改模块,操作的都是模块本身,都是以原模块为基准的,与调用位置无关
print(foo.x)
print(foo.func)
print(foo.func1)
# 引用多个模块:可以以逗号为分隔符,在一行导入多个模块,但是还是建议逐行导入,不建议在一行导入多个模块
导入模块的规范
# 1 内置模块
# 2 第三方库的模块
# 3 自定义模块
import ... as ....
# 将调用的模块起别名,当模块名较长的时候可以用
import foo as f
模块是第一类对象,和函数对象类似,可以用来给给变量赋值
自定义模块的命名应该采用纯小写+下划线的风格
可以在函数内导入模块,但是函数调用结束后,对应的模块占用的名称空间和内存空间会销毁
二 import 导入自定义模块
import foo
# 如何引用模块名称空间中的名字?---需要在模块名称空间中的名字加上前缀,即模块的名字+.
a = foo.x
print(a)
print(foo.get)
foo.change()
print(foo.x)
首次导入模块发生的事情
1 执行模块文件中的源代码
2 产生新的名称空间用于存放模块文件执行过程中产生的名字
3 在执行文件所在的名称空间中得到模块文件的名字,该名字指向模块文件的名称空间
注意:只有第一次导入模块才会执行模块代码
三 from...import...导入自定义模块
# 从foo中导入需要使用的名字,但是此时从模块中导入的名字已经是执行文件中的全局名称,所以可能会出现名字混淆的状况
from foo import x
from foo import get
from foo import change
print(x) # 此时输出的是foo文件中x对应的内存地址111的值
change() # 执行change函数,将全局变量x与111的内存地址解绑,然后与值0的内存地址绑定
print(x) # 此时执行文件中全局变量x指向的内存地址还是111的内存地址
from foo import x # 重新导入模块,全局变量的x指向的内存地址已经是0的内存地址
print(x) # 此时执行文件中全局变量x指向的内存地址也是0的内存地址
from ... import导入也发现三件事
1 产生模块的内存空间
2 运行模块文件将运行过程中产生的名字都丢到模块的名称空间去
3 在当前名称空间拿到一个名字,该名字指向模块名称空间中的某一个内存地址
四 两种导入方式对比
import 导入模块在使用时必须加前缀'模块.'
优点:肯定不会与当前名称空间的名字冲突
缺点:加前缀显得麻烦
from...import...
优点:可以不用加前缀,代码更精简
缺点:容易与当前名称空间的名字混淆
五 循环导入
循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常
m1.py
print('正在导入m1')
from m2 import y
x='m1'
m2.py
print('正在导入m2')
from m1 import x
y='m2'
run.py
import m1
测试一
#1、执行run.py会抛出异常
正在导入m1
正在导入m2
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module>
import m1
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
from m1 import x
ImportError: cannot import name 'x'
#2、分析
先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"
--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
测试二
#1、执行文件不等于导入文件,比如执行m1.py不等于导入了m1
直接执行m1.py抛出异常
正在导入m1
正在导入m2
正在导入m1
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
from m1 import x
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
ImportError: cannot import name 'y'
#2、分析
执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错
解决方案
# 方案一:导入语句放到最后,保证在导入时,所有名字都已经加载过
# 文件:m1.py
print('正在导入m1')
x='m1'
from m2 import y
# 文件:m2.py
print('正在导入m2')
y='m2'
from m1 import x
# 文件:run.py内容如下,执行该文件,可以正常使用
import m1
print(m1.x)
print(m1.y)
# 方案二:导入语句放到函数中,只有在调用函数时才会执行其内部代码
# 文件:m1.py
print('正在导入m1')
def f1():
from m2 import y
print(x,y)
x = 'm1'
# 文件:m2.py
print('正在导入m2')
def f2():
from m1 import x
print(x,y)
y = 'm2'
# 文件:run.py内容如下,执行该文件,可以正常使用
import m1
m1.f1()
注意:循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入
六 一个py文件的两种用途一个python文件有两种用途
1 被当作程序/脚本运行
2 被当作模块导入
为了区别同一个文件的不同用途,每个py文件都内置了__name__变量,该变量在py文件被当做脚本执行时赋值为“__main__”,在py文件被当做模块导入时赋值为模块名
作为模块foo.py的开发者,可以在文件末尾基于__name__在不同应用场景下值的不同来控制文件执行不同的逻辑
def func():
print('我是func')
if __name__ == '__main__':
func()
七 模块查找优先级
无论是import 还是from...import...导入模块都涉及到查找模块路径的问题
模块查找的优先级
1 内存
2 硬盘:按照sys.path存放的文件夹的顺序依次查找
Ⅰ第一个文件夹就是执行文件所在的文件夹
Ⅱsys.path存放的文件夹的顺序以解释器为准,不要以pycharm为准
sys.path中的第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为/pythoner/projects/
import sys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)
import foo #无论foo.py在何处,我们都可以导入它了
包
一 包的介绍
1 什么是包
包就是一个含有__init__.py文件的文件夹
在导包的时候,导入的其实__init__.py文件
包的本质是模块的一种形式
2 为何要有包
随着模块数目的增多,把所有模块不加区分地放到一起也是极不合理的,于是Python为我们提供了一种把模块组织到一起的方法,即创建一个包。
本质是模块,所以用来当作模块导入
二 包的使用
1 导入包与___init___.py
1 首次导入会运行包内__init__文件内的代码
2 产生__init__文件的名称空间,将运行__init__文件时产生的名字都求导该名称空间中
3 在执行文件中得到一个全局名称----包的名字(bag),该名字指向__init__.py文件的名称空间
import bag
print(bag.x) # 相当于__init__.x
from bag import x
print(x)
1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如import 顶级包.子包.子模块,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
2、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
3、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
2 绝对导入与相对导入
Ⅰ 绝对导入(包内导入其他模块)
import sys
print('__init__导入模块的查找顺序')
print(sys.path)
# from import导入
from foo.foo1 import f1
from foo.foo2 import f2
from foo.foo3 import f3
from foo.func.foo4 import f4
在__init__.py文件中导入模块,查找顺序也是参照执行文件的sys.path,即哪个执行文件导了本模块,那么本模块在导入其他模块时查找其他模块的顺序就是参照哪个执行文件的sys.path
大的前提:作为模块的设计者你需要知道,模块的使用者在导入模块的时候,如果模块与执行文件不在同一个文件夹下,使用者一定会将模块所在的文件夹加到sys.path路径中
对设计者来说,在__init__.py文件中导入其他子模块,查找子模块的顺序也是参照执行文件的sys.path,而此时使用者已经将模块的文件夹路径添加到sys.path,
所以只需要在sys.path列表中的文件夹中寻找,而使用者在下载模块的时候都是在一个文件夹中的,即模块所在的文件夹是封装好的,所以以包的文件夹作为起始,
然后寻找其他子模块,将其他子模块导入到__init__.py中即可,当设计者需要跨文件引用其他包的子模块时,只要将其他包的文件夹路径加入到syspath即可
Ⅱ 相对导入
上述是绝对导入的方式,下面介绍相对导入
相对导入主要用于包内每个子模块的py文件互相引用
.:代表当前文件夹,此时就是foo文件夹
..:代表上一层文件夹
注意:相对导入仅限于包内使用,不能跨出包,否则会出现语法错误
包内的相互导入推荐使用相对导入
from .foo1 import f1
from .foo2 import f2
from .func.foo3 import f3
Ⅲ 两种导入方式对比
两种导入模式的优缺点:
绝对导入:
以顶级的包为起始,点的形式往下找子包,一定能找到
更改包名或着模块名的时候,导入的方式全部要改变
能够导入任意地方的包
相对导入:
包内模块彼此之间的导入,推荐使用相对导入
当对包名更改时,因为只考虑在包内的相对位置
不能跨出包
软件开发目录规范
• core/: 存放业务逻辑相关代码
• api/: 存放接口文件,接口主要用于为业务逻辑提供数据操作。
• db/: 存放操作数据库相关文件,主要用于与数据库交互
• lib/: 存放程序中常用的自定义模块
• conf/: 存放配置文件
• run.py: 程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹作为sys.path的第一个路径,这样就省去了处理环境变量的步骤
• setup.py: 安装、部署、打包的脚本。
• requirements.txt: 存放软件依赖的外部Python包列表。
• README: 项目说明文件。