在Python中,一个py文件就是一个模块,文件名为xxx.py模块名则是xxx,导入模块可以引用模块中已经写好的功能。如果把开发程序比喻成制造一台电脑,编写模块就像是在制造电脑的零部件,准备好零部件后,剩下的工作就是按照逻辑把它们组装到一起
将程序模块化会使得程序的组织结构清晰,维护起来更加方便。比起直接开发一个完整的程序,单独开发一个小的模块也会更加简单,并且程序中的模块可以被重复使用
所以总结下来,使用模块既保证了代码的重用性,又增强了程序的结构性和可维护性。另外除了自定义模块外,我们还可以导入使用内置或第三方模块提供的现成功能,这种“拿来主义”极大地提高了程序员的开发效率
'文件名:foo.py'
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
要想在另外一个py文件中引用foo.py中的功能,需要使用 import foo,首次导入模块会做三件事:
1、产生一个新的名称空间用于存放源文件执行过程中产生的名字
2、执行源文件代码,将定义的所有变量名放到名称空间中去
3、在当前执行文件所在的名称空间中得到一个名字foo,该名字指向新创建的模块名称空间,若要引用模块名称空间中的名字,需要加上该前缀
'文件名:new.py'
import foo #导入模块foo
a = foo.x #引用模块foo中变量x的值赋值给当前名称空间中的名字a
foo.get() #调用模块foo的get函数
foo.change() #调用模块foo中的change函数
obj=foo.Foo() #使用模块foo的类Foo来实例化,进一步可以执行obj.func()
加上 foo. 作为前缀就相当于指名道姓地说明要引用foo名称空间中的名字,所以肯定不会与当前执行文件所在名称空间中的名字相冲突,并且若当前执行文件的名称空间中存在x,执行foo.get ( )或foo.change ( )操作的都是源文件中的全局变量 x
用import语句导入多个模块,可以写多行import语句, 还可以在一行导入,用逗号分隔开不同的模块
import module1
import module2
import moduleN
import module1,module2,...,moduleN
使用import foo导入模块后,引用模块中的名字都需要加上foo.作为前缀
而 使用from foo import x, get, change, Foo则可以在当前执行文件中直接引用模块foo中的名字
from foo import x,get,change #将模块foo中的 x,get,change 导入到当前名称空间
a = x #直接使用模块foo中的x赋值给a
get() #直接执行foo中的get函数
change() #即便是当前有重名的x,修改的仍然是源文件中的x
无需加前缀的好处是使得我们的代码更加简洁
坏处则是容易与当前名称空间中的名字冲突,如果当前名称空间存在相同的名字,则后定义的名字会覆盖之前定义的名字
另外from语句支持 from foo import * 语法,* 代表将 foo中所有的名字都导入到当前位置
from foo import *
# 把foo中所有的名字都导入到当前执行文件的名称空间中,在当前位置直接可以使用这些名字
a = x
get()
change()
obj = Foo()
如果我们需要引用模块中的名字过多的话,可以采用上述的导入形式来达到节省代码量的效果,但是需要强调的一点是:只能在模块最顶层使用 * 号的方式导入,在函数内则非法
并且 * 号的方式 会带来一种副作用,即我们无法搞清楚究竟从源文件中导入了哪些名字到当前位置,这极有可能与当前位置的名字产生冲突
模块的编写者可以在自己的文件中定义 _ _ all _ _变量用来控制 * 代表的意思,该方法是模块中内置的方法
#foo.py
__all__=['x','get']
# 该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
这样我们在另外一个文件中使用*导入时,就只能导入 _ _ all _ _定义的名字了
from foo import * #此时的*只代表x和get
x #可用
get() #可用
change() #不可用
Foo() #不可用
可以在当前位置为导入的模块起一个别名,还可以为导入的一个名字起别名
import foo as f #为导入的模块foo在当前位置起别名f,以后再使用时就用这个别名f
f.x
f.get()
from foo import get as get_x
get_x()
通常在被导入的名字过长时采用起别名的方式来精简代码
另外为被导入的名字起别名可以很好地避免与当前名字发生冲突
还有很重要的一点就是:可以保持调用方式的一致性
例如我们有两个模块json和pickle同时实现了load方法,作用是从一个打开的文件中解析出结构化的数据,但解析的格式不同,可以用下述代码有选择性地加载不同的模块
if data_format == 'json':
import json as serialize #如果数据格式是json,那么导入json模块并命名为serialize
elif data_format == 'pickle':
import pickle as serialize #如果数据格式是pickle,那么导入pickle模块并命名为serialize
data = serialize.load(fn) #最终调用的方式是一致的
"在 run.py 文件"
import m1
'在 m1.py 文件'
print('正在导入m1')
from m2 import y
x=111
'在 m2.py 文件'
print('正在导入m2')
from m1 import x
y=222
'正在导入m1'
'正在导入m2'
'ImportError' # 报错
在 run.py 中开始运行 -->产生一个 run.py 的名称空间用于存放 run.py 文件执行过程中产生的名字
-->首次导入 m1.py–>执行m1.py文件代码
在 m1.py 中开始运行 -->产生一个 m1.py 的名称空间用于存放 m1.py 文件执行过程中产生的名字
-->首次导入 m2.py–>执行m2.py文件代码
(此时,并没有 运行到 定义 x=111 这行代码)
在 m2.py 中开始运行 -->产生一个 m2.py 的名称空间用于存放 m2.py 文件执行过程中产生的名字
--> m1.py 并不是首次导入,已经生成了一个m1.py 的名称空间
--> 从m1.py 的名称空间中找x, 并没有 定义 x=111 这行代码, 所以找不到 x
1、将 导入语句放在 最后,保证所有的名字都已经被加载过 (很明显,这种方式会导致,文件的结构很混乱,不推荐 )
2、导入语句放到函数中,只有在调用函数时才会执行其内部代码
# 文件:m1.py
print('正在导入m1')
def f1():
from m2 import y
print(x,y)
x = 111
# 文件:m2.py
print('正在导入m2')
def f2():
from m1 import x
print(x,y)
y = 222
# 文件:run.py内容如下,执行该文件,可以正常使用
import m1
m1.f1()
在 run.py 中开始运行 -->产生一个 run.py 的名称空间用于存放 run.py 文件执行过程中产生的名字
第一步:import m1 -->首次导入 m1.py
第二步:执行m1.py文件代码 -->产生一个 m1.py 的名称空间用于存放 m1.py 文件执行过程中产生的名字
第三步:定义了一个函数 f1,没有运行、定义了 x = 111;
(此时,m1.py 中的已经全部运行完,然后返回到 run.py中,继续运行 run.py 中剩下的代码)
第四步:运行 m1.f1( )–>调用了 函数 f1–>导入 m2.py 后,并且运行结束然后 再打印x,y
第五步:首次导入 m2.py,产生 m2 的名称空间,并运行m2.py 中的代码
第六步:m2.py 中,定义了一个函数 f2, 再定义了 y=222,至此 m2.py 运行结束–> 继续执行 f1
第七步,打印 x, y ;x=111,y=222
在导入一个模块时,
1、如果该模块已加载到内存中,则直接引用
2、否则会优先查找内置模块
3、然后按照从左到右的顺序依次检索sys.path中定义的路径,直到找模块对应的文件为止,否则抛出异常
sys.path也被称为模块的搜索路径,它是一个列表类型,列表中的每个元素其实都可以当作一个目录来看
import sys
print(sys.path)
['/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages', '/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm_matplotlib_backend',
...
]
sys.path中的第一个路径通常代表 执行文件 所在的文件夹,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的
被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为/pythoner/projects/
import sys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)
此时,无论foo.py在何处,我们都可以导入它
在上面我们已经解决了导入的模块与执行文件在不同路径下,模块对应的源文件如何被找到的问题
但是,文件是存储在我们自己的计算机中,如果文件被别人下载之后,面临着文件路径改变的问题
所以,需要找到一种方法,将文件的路径给 “写活” ,这样不论怎么换计算机都不会出现路径错误
import sys
import os
print(os.path.dirname(__file__))
' dirname --> 拿到 括号内文件 的文件夹的路径'
'作用:拿到当前文件的 上级目录的 路径'
'如果还想拿再上一级--->os.path.dirname(os.path.dirname(__file__)) '
sys.path.append(os.path.dirname(__file__)) # 当前文件所处的目录文件夹加入了环境变量
注意:执行文件所处的文件夹会被默认加入环境变量中
_ _ file _ _ 是当前文件的路径,而不是执行文件的路径
一个Python文件有两种用途,一种被当主程序/脚本执行,另一种被当模块导入
为了区别同一个文件的不同用途,每个py文件都内置了_ _ name _ _ 变量,
该变量在py文件被当做脚本执行时赋值为“ _ _ main _ _”,
在py文件被当做模块导入时赋值为模块名
#foo.py
...
if __name__ == '__main__':
# foo.py被当做 脚本执行 时运行的代码
pass
else:
# foo.py被当做 模块导入 时运行的代码
pass
通常我们会在 if 的子代码块中编写针对模块功能的测试代码
这样foo.py在被当做脚本运行时,就会执行测试代码,而被当做模块导入时则不用执行测试代码