一 ,模块(module)概述
在计算机程序开发中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护.
为了编写可维护的代码,可以把函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,在Python中一个.py文件称为一个模块
最大的好处是大大提高了代码的可维护性。
其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。
所以,模块一共三种:
- python标准库
- 第三方模块
- 应用程序自定义模块
另外,使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。
二 模块的使用
2.1 import语句
若想在一个.py文件中引入另一个.py文件的功能,需要使用 import 目标模块名,首次导入模块会做三件事:
①执行目标模块中的代码
②产生一个新的名称空间用于存放目标模块执行过程中产生的名字(即目标模块名)
③在当前执行文件所在的名称空间中得到一个名字(即目标模块名),该名字指向新创建的模块名称空间,若想引用模块名称空间的名字,需要加上该前缀,如下:
import foo #导入模块foo
a=foo.x #引用模块foo中变量x的值并赋给当前名称空间中的名字a
注:
①当我们使用import语句的时候,Python解释器是怎样找到对应的文件的呢?答案就是解释器有自己的搜索路径,存在sys.path里。
②第一次导入模块已经将其加载到内存空间了,之后的重复导入会直接引用内存中已存在的模块,不会重复执行文件,通过sys模块中的,sys.modules可以看到内存中已经加载的模块名.
③模块名应该遵循小写形式,标准库从python2过渡到python3做出了很多这类调整,如Queue全更新为纯小写模式.
④我们导入的模块中可能包含有python内置的模块,第三方模块,自定义模块,为了便于明显地区分它们,通常在文件的开头导入模块,并且分类导入,不同类别的导入顺序如下:
import Python内置模块
import 第三方模块
import 程序员自定义模块
2.2 from 模块名 import 函数名 as 别名
与import将整个模块导入所不同的是,使用from则是将模块中某一个函数或者名字导入,而不是整个模块,还有一个不同之处是:使用import的导入模块,要使用模块中的函数则必须以模块名加“.”,然后是函数名的形式调用函数;而使用from导入模块中的某个函数,则可以直接使用函数名调用,不用在前面加上模块名称.
此外,使用from导入时,函数名处可以只用一个"*"来表示导入该模块中所有代码,但这种方式会带来一种副作用,即我们无法搞清楚究竟从源文件中导入了哪些名字到当前位置,这极有可能与当前位置的名字产生冲突.模块的编写者可以在自己的文件中定义__all__变量来控制*代表的意思,如下所示:
--------------以下是foo.py的代码--------------
__all__=['x','get']
x=1
def get():
print(x)
def change():
print('change 函数')
------------在别的模块中------------
from foo imprt *
x #在其他模块中可用
get() #在其他模块中可用
change() #在其他模块中不可用
2.3 循环导入问题
循环导入问题指的是一个模块加载/导入过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败.,抛出异常,究其根源就是在python中,同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码.
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会抛出异常
Traceback (most recent call last):
正在导入m1
File "D:/代码/MyDjango/Python基础学习/run.py", line 1, in <module>
import m1
正在导入m2
File "D:\代码\MyDjango\Python基础学习\m1.py", line 2, in <module>
from m2 import y
File "D:\代码\MyDjango\Python基础学习\m2.py", line 2, in <module>
from m1 import x
ImportError: cannot import name 'x'
#分析
先执行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
正在导入m2
正在导入m1
Traceback (most recent call last):
File "D:/代码/MyDjango/Python基础学习/m1.py", line 2, in <module>
from m2 import y
File "D:\代码\MyDjango\Python基础学习\m2.py", line 2, in <module>
from m1 import x
File "D:\代码\MyDjango\Python基础学习\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.py并不等于导入了m1,于是开始导入m1并执行其内部代码-->''打印正在导入m1",
执行 from m2 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()
解决方案
2.4探索模块路径的优先级
模块其实分为四个通用类型,分别是:
①使用纯Python代码编写的.py文件;
②包含一系列模块的包;
③使用C编写并链接到Python解释器中的内置模块;
④使用C或C++编译的扩展模块;
在导入一个模块时,如果该模块已加载到内存中,则直接引用,否则会优先查找内置模块,然后从左往右的顺序依次检索sys.path中定义的路径,直到找到模块对应的文件为止,否则抛出异常.sys.path也被称为模块的搜索路径,它是一个列表类型.
>>> sys.path
['', 'D:\\Python\\python36.zip', 'D:\\Python\\DLLs', 'D:\\Python\\lib', 'D:\\Python', 'D:\\Python\\lib\\site-packages']
列表中每一个元素都可以当做一个目录来看:在列表中会发现有.zip或.egg结尾的文件,二者是不同形式的压缩文件,事实上Python确实支持从一个压缩文件中导入模块,只需把他们当成目录看即可.
在Python IDLE中执行sys.path,sys.path中第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件所在的路径添加到sys.path中,假设foo.py所在的路径为/test/projiects/
import sys
sys.path.append(r'/test/project')#也可以使用sys.path.insert(....)
import foo #无论foo.py在何处,都可以导入它了
2.5 区分py文件的两种用途
一个Python文件有两种用途,一种被当主程序/脚本执行.另一种被当模块导入,为了区别同一个文件的不同用途,每个.py文件都内置了__name__变量,该变量在py文件被当做脚本执行时赋值为"__main__"在py文件被当做模块导入时赋值为模块名
2.6 规范模块的示例
#!/usr/bin/env python #通常在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器
#-*coding:utf-8-*-
"模块的文档描述"
import sys #导入模块
x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
class Foo: #定义类,并写好类的注释
pass
def test():#定义函数,并写好函数的注释
pass
if __name__ == "__main__":#主程序
test() #在被当做脚本执行时m执行此处代码