Python-模块
一、基本概念
什么是模块?
模块就是一系列功能的集合体,分为三大类:
- 内置的模块
- 第三方的模块
- 自定义的模块
模块有四种形式:
- 使用python编写的.py文件(一个python文件本身就一个模块,文件名为m.py,模块名就叫m)
- 已被编译为共享库或DLL的C或C++扩展
- 把一系列模块组织到一起的文件夹(注:文件夹下有一个_ _init_ _.py文件,该文件夹称之为包)
- 使用C编写并链接到python解释器的内置模块
为何要用模块?
- 内置与第三方的模块拿来就能用,无需定义。这种拿来主义,可以极大地提升自己的开发效率。
- 自定义的模块可以将程序的各部分所共用功能提取出来,放到一个模块中让大家共享使用,好处是减少了代码冗余,程序组织结构更加清晰。
二、模块的使用
定义模块
文件名:foo.py
文件内容:
print('模块foo前来报到')
x = 1
def get():
print(x)
def change():
global x
x = 0
我们现在定义了一个模块,模块名为foo。
要注意:自定义模块的命名应该采用纯小写+下划线的格式
导入模块
要想在另外一个py文件中引用foo模块中的功能,需要使用import foo导入。
import foo # 模块foo前来报到
我们可以看到模块成功导入,并执行了模块代码。
当我们需要导入多个模块的时候,可以用逗号为分隔符在一行导入多个模块:
import time, foo, m
# 但不建议这么做,因为我们有导入模块的规范。
导入模块的规范:
我们导入的模块中可能包含有python内置的模块、第三方的模块、自定义的模块。为了便于明显地区分它们,我们通常在文件的开头导入模块,并且分类导入。一类模块的导入与另外一类的导入用空行隔开,不同类别的导入顺序如下:
- python内置模块
- 第三方模块
- 程序员自定义模块
import time
import sys
import 第三方1
import 第三方2
import 自定义模块1
import 自定义模块2
import 自定义模块3
首次导入模块(重复导入无效)会发生什么事情?
-
执行模块的源文件代码
-
产生一个新的名称空间用于存放模块的源文件执行过程中产生的名字
-
在当前执行文件所在的名称空间中得到一个名字foo,该名字指向新创建的模块名称空间,若要引用模块名称空间中的名字,需要加上该前缀,如下:
import foo a = foo.x # 引用模块foo中变量x的值赋值给当前名称空间中的名字a foo.get() # 调用模块foo的get函数 foo.change() # 调用模块foo中的change函数 print(foo.x) # 运行结果: # 模块foo前来报到 # 1 # 0
在首次导入模块之后重复导入相同模块的行为,都是直接引用首次导入产生的模块名称空间,不会重复执行代码。
导入模块之后的引用
-
“ 模块名 . 名字 ”,是指名道姓地问某一个模块要名字对应的值,不会与当前名称空间中的名字发生冲突。
import foo x = 1111111111111 print(x) print(foo.x) # 运行结果: # 模块foo前来报到 # 1111111111111 # 1
-
无论是查看还是修改,操作的都是模块本身,与调用位置无关。(名称空间的相互关系是在定义的时候决定的)
import foo x = 1111111111111 foo.get() # 1 foo.change() # foo.py里的x被修改 foo.get() # 0
其他导入语法
-
import 模块名 as 自定义名
只用 import导入模块的方法在使用时必须加前缀 "模块 ."
优点:肯定不会与当前名称空间中的名字冲突
缺点:加前缀显得麻烦
import 模块名 as 自定义名:
通常在被导入的名字过长时采用起别名的方式来精简代码,另外为被导入的名字起别名可以很好地避免与当前名字发生冲突。
import foo as f f.get() # 1
-
可以在函数内导入模块
def func(): import foo
-
from…import…
from…import…与import语句基本一致,唯一不同的是,使用import foo导入模块后,引用模块中的名字都需要加上foo.作为前缀;而使用from foo import x, get, change 的形式,则可以在当前执行文件中直接引用模块foo中的名字,如下:
from foo import x,get,change # 将模块foo中的x、get、change导入到当前名称空间 a=x # 直接将模块foo中的x指向的内存地址赋值给当前文件全局名称空间的a(让a指向x指向的内存地址) get() # 直接执行foo中的get函数 change() # 即便是当前文件中有重名的x,修改的仍然是源文件中的x
如果当前名称空间存在相同的名字,则后定义的名字会覆盖之前定义的名字,如下:
x = '惊喜' from foo import x print(x) # 1
from … import …导入也发生了三件事:
-
产生一个模块的名称空间
-
运行foo.py,将运行过程中产生的名字都丢到模块的名称空间去
-
在当前文件的名称空间中产生一个名字,该名字与模块名称空间中相同的名字指向同一个内存地址
from foo import x # x = 模块foo中值1的内存地址 from foo import get from foo import change print(x) # 1 print(get) # <function get at 0x000002696B0B4700> print(change) # <function change at 0x000002696B0B4670> x = 333333333 print(x) # 333333333 change() get() # 0 print(x) # 333333333
from foo import x # x = 模块foo中值1的内存地址 from foo import get from foo import change print(x) # 1 change() # 只改变的是模块foo中x的值,当前文件名称空间中的x在导入模块的时候绑定的值是1,模块内x绑定的值再更改不会影响当前文件名称空间中的x。 get() # 0 print(x) # 1
使用 from…impot…导入模块在使用时不用加前缀
优点:代码更精简
缺点:容易与当前名称空间混淆
from…import * 导入模块中的所有名字:
from foo import * print(x) print(get) print(change) # 运行结果: # 模块foo前来报到 # 1 # <function get at 0x000001B59AD14160> # <function change at 0x000001B5AB774700>
如果我们需要引用模块中的名字过多的话,可以采用上述的导入形式来达到节省代码量的效果,但是需要强调的一点是:只能在模块最顶层使用 * 的方式导入,在函数内则非法。并且 * 的方式会带来一种副作用,即我们无法搞清楚究竟从源文件中导入了哪些名字到当前文件,这极有可能与当前位置的名字产生冲突。
所以模块的编写者可以在自己的文件中定义_ _all_ _变量用来控制 * 代表的名字范围:
# 在 foo.py 中添加: _ _all_ _ = ['x'] # 控制 * 代表的名字有哪些
在当前文件导入:
from foo import * # 此时的 * 只代表 x print(x) # 1 print(get) # 报错:NameError: name 'get' is not defined print(change) # 报错:NameError: name 'change' is not defined
利用as起别名:
from foo import get as g g() # 运行结果: # 模块foo前来报到 # 1
-
三、一个Python文件的两种用途
一个Python文件有两种用途
- 被当成程序运行
- 被当做模块导入
二者的区别是什么?==》“_ _name_ _”
- 当 foo.py 被运行时,_ _name_ _的值为’ _ _main_ _ ’
- 当 foo.py 被当做模块导入时,_ _name_ _的值为’ foo ’
if __name__ == '__main__':
print('python文件运行') # 直接运行foo.py文件输出这句话
else:
print('文件被当做模块导入') # 被当做模块通过import导入输出这句话
四、模块查找的优先级
无论是import还是from…import在导入模块时都涉及到查找的问题
查找的优先级:
- 内存(内置模块)
- 硬盘:按照sys.path中存放的路径顺序依次查找要导入的模块
在导入一个模块时,如果该模块已加载到内存中,则直接引用,否则会优先查找内置模块,然后按照从左到右的顺序依次检索sys.path中定义的路径,直到找到模块对应的文件为止,否则抛出异常。
sys.path也被称为模块的搜索路径,它是一个列表类型:
import sys
print(sys.path)
# 值为一个列表,存放了一系列的对文件夹
# 其中第一个文件夹是当前执行文件所在的文件夹
若找不到想要导入的模块,可以将模块所在的文件夹的绝对路径添加到sys.path列表中
import sys
# 找foo.py就把foo.py的文件夹的绝对路径添加到环境变量中
sys.path.append(r'/Users/用户/PycharmProjects/Demo/test/8.20')
注意:这种添加是临时添加,程序运行结束后会恢复原样。
使用sys.modules查看已经加载到内存中的模块
import sys
# print('foo' in sys.modules) # False
print(sys.modules)
五、编写模块的规范
我们在编写模块的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() # 在被当做脚本执行时,执行此处的代码