python深入之包和模块的导入机制(重点)

关于模块导入时模块底层做的事情

(1)第一次导入模块:底层做了3件事情,1.在自己的命名空间执行被导入模块中的所有代码;2.以模块名为名称创建一个模块对象,并将模块中所有的顶级变量(包括变量和函数)以属性的形式绑定在该模块对象上;3.在import位置引入该对象名称到当前命名空间。这里,在当前命名空间使用被导入模块中的属性时要使用“.”语法的原因,就可以解释为要使用一个对象的某个属性,就必须使用“对象.属性”的形式

(2)第二次导入模块:直接执行第3步,即在import位置引入该对象名称到当前命名空间。原因是第一次导入之后,已经将前两步执行的结果存储到内存中,第二低导入时直接到相应的内存寻找即可,不需浪费更多内存于此。因此,第二次导入速度更快,且更节省内存

(3)import M和from A import B两种导入方式在底层执行机制的异同(重点):两种导入方式第一次导入都会执行上述3步,第二次或更多次导入则只会执行第三步,所以两种导入方式没有谁比谁更节省内存之说,不要认为后者只是导入了模块中的一部分就觉得后者比较省内存,其实际是都会首先执行被导入模块中的所有内容,占的内存是相同的,区别在于是拿被导入模块中的哪些部分到当前命名空间中进行使用

关于被导入模块位置的检索

(1)第一次导入模块:按照模块检索路径顺序去寻找,即第一级是内置模块,如sys模块,这类模块优先级最高;第二级是sys.path路径列表。其中sys.path路径列表路径列表由四部分组成,分别是1.当前目录;2.环境变量PYTHONPATH中指定的路径列表;3.指定路径下的.pth文件中的文件路径列表;4.python的安装路径及其中的LIB库

(2)修改模块检索路径

一.直接修改sys.path。由于sys.path是一个列表,而列表是可变的,所以可以利用列表方法append(“路径”)对sys.path进行修改,使得自定义的模块可以从sys.path中被检索到。这里需要注意的是,修改的sys.path只能使用在当前命名空间中,在其他模块需要重新再修改才能再次使用,即单次有效

二.直接在环境变量PYTHONPATH中的路径列表中添加路径。在电脑设置环境变量中可以进行添加,这里是在“用户变量”一栏中对PYTHONPATH进行添加,以及添加其值为需要的模块路径,添加完后,路径会自动出现在sys.path路径列表当中

三.添加.pth文件。.pth文件要在指定的特殊位置进行添加,这里需要使用site模块中的geisitepackage()方法进行指定特殊位置的查询,一般是python安装路径和安装路径中lib目录下的site-package目录这两个位置。在这两个位置其中一个里面创建.pth文件,并在文件中添加需要的路径,如果要添加多个路径,通过换行即可。之后改路径会自动出现在sys.path路径列表当中

(3)sys.path路径列表中的优先级(重点):当前目录--环境变量PYTHONPATH--python安装目录--python安装目录中的.pth文件--site-package目录--site-package目录中的.pth文件

(4)第二次导入模块:直接到加载过的模块群中去找,这里需要使用sys模块中的modules属性来查看哪些模块是被加载过的

关于导入模块的常见场景

(1)局部导入:即在某些局部范围内(小的命名空间)中对一些模块进行导入,避免耗时。原因在于一旦对某个模块进行导入,就会先去执行该模块的所有内容,如果该模块内容繁多,而在当前命名空间中又只在少数地方会用到该模块,就应该在用到该模块的命名空间中再对其进行导入,避免一开始就用掉很多内存,且比较耗时。需要注意,局部导入的模块只能在局部使用,外部不能使用,如:

def run():
    import other
    print(other.o1)
    print(other.o2)

这里只有在调用run()方法时才会用到other模块的内容,故只在run()方法内部进行模块的导入,也只能在run()方法内部使用,其他地方不能使用

(2)覆盖导入:即自定义模块与其他模块重名时,其导入遭到覆盖的情况。有两种情况,1.自定义模块与非内置模块重名,则由优先级别的高低,可能会被覆盖;2.自定义模块与内置模块重名,则一定会被覆盖,这里可以使用from A import B的导入形式对与内置模块重名的模块进行导入

(3)循环导入:有两个模块A和B,在A中导入了B,在B中又导入了A,这就是循环导入。循环导入会导致最终的结果出现错误,举个例子详细说明:

两个模块分别是test和other,test模块中的内容是:

t1 = "t1"
t2 = "t2"
import other
print(other.o1)
print(other.o2)

other模块中的内容是:

o1 = "o1"
o2 = "o2"
import test
print(test.t1)
print(test.t2)

运行test模块,得到的结果是:

o1
o2
t1
t2
o1
o2

其中的执行过程详解:1.执行test中的代码,直到import other;2.去sys.modules中寻找other模块是否已经被加载过,这里都是第一次导入,other没有加载过,去检索路径寻找other模块,执行其中代码,并将其中的顶层变量o1和o2以属性的形式绑定在other对象上,存入sys.modules中;3.执行other代码到import test,重复2中内容,将绑定了t1和t2的test对象存入sys.modules;4.执行test代码到import other,这时,other是已经加载过的模块,并且绑定了o1和o2两个属性,在sys.modules中找到other模块后,不再去路径列表中寻找,直接执行其下的print(other.o1)和print(other.o2)语句,打印出o1和o2;5.4完成即是3中的import test完成,继续后面的print(test.t1)和print(test.t2)语句,打印出t1和t2;6.5完成即是1中的import other完成,继续执行后面的print(other.o1)和print(other.o2)语句,再次打印出o1和o2,即为最后的结果

循环导入问题的解决方案:避免出现循环导入,将内容放到其他模块中,再分别进行导入即可,这样就不会出现两个模块相互导入的情况

(4)可选导入:即两个功能相近的模块,根据需要选择其中一个进行导入。使用场景是想要优先选择A模块,但是要在没有A模块的情况下,将B模块当作备用,实现代码如下:

try:
    import A as o
except ModuleNotFoundError:
    import B as o
print(o.o1)

给两个模块取相同的别名,这样不管是用哪一个,都可以统一使用同一个名字来调用,增加了代码的扩展性

猜你喜欢

转载自blog.csdn.net/zx870121209/article/details/81536267
今日推荐