目录

 

import和from的等价关系

import的工作原理

from进行引用复制

搜索路径顺序和相对导入

模块命名空间的更新原则

import和from的等价关系

       python中,import和from都可以导入模块,import是将整个模块导入并构建模块对象,模块对象就是一个命名空间,其有自己的独立的作用域;而from看似是导入模块中某些属性,但其实依然是导入整个模块,然后把部分属性对象的引用值复制到主模块的作用域中。更具体的,from module import x,y 这条语句等价于下面的语句:

import module
x=module.x
y=module.y
del module

       所以,对于from 和import之间的区别,实际上只需要看上面的等价的代码就可以了,from相比于import只是额外的多了对指定变量进行引用值的复制这一步,并且还删除了module对象,因此我们无法在主模块中获取module对象,也就无法获取module对象的其他属性。

import的工作原理

       对于import的工作原理,其会在第一次导入一个模块的时候,逐条的运行被导入模块中的语句,并构建一个模块对象,这个模块对象是一个命名空间,可以通过点号访问该对象的属性;将该模块对象会被保存在sys.modules中,sys.modules是一个字典,键是在该解释器中被导入的所有模块的名称,值是该模块的描述,包括模块在硬盘中地址的记录。要注意的是,对于被导入模块的运行,只会在其在该解释器中第一次被导入的时候才会被运行,之后如果其再次被导入,便不会再被运行;更具体的讲,一个模块被import,python会先检查sys.modules这个字典中是否有这个模块名称,如果有则会直接调用,不再重新导入运行;因此,一个模块往往只在第一次被导入时才会被运行,之后的导入都会直接调用该对象,不会再运行;当然,如果我们想要重新导入在运行,比如有时候我们更新了某个模块,这时可以用reload内置函数(2.x)或者标准模块imp中的reload函数(3.x)进行重新导入。

       这里需要特别注意的是,对于from,尽管其和import有上述的等价关系,但是del module语句并不是说将module这个对象完全删除了,其只是减少了其在模块中的一次引用,但是该模块对象在内存中依然是存在的,只有当一个对象的引用次数为0时才会被解释器回收,但是该对象依然在sys.modules中还有引用,因此该模块对象依然在内存中。对于对于from的导入,实际上并不仅仅导入了模块中特定的属性,而是整个模块依然是被完整导入了的,虽然我们无法直接在主模块中访问该对象,但是可以通过sys.modules['modulename']的方式获取该模块对象,然后再通过点号获取其所有的属性。所以,我们尽管首先进行了from式的导入,看似是导入部分属性,但是后面再通过import导入同一模块的时候,由于sys.modules中有该模块对象,因此不会再次导入再次运行。

from进行引用复制

       此外,更隐晦的一点是,由于from语句复制的是引用,两个变量的引用指向的对象值是一样的,因此如果被from导入的是一个可变对象,且对其进行了修改,那么后续import的同一模块的相应属性也会被修改,看如下例子。

#假设module.py的内容为
x=1
y=[2]

#先from导入y
from module import y
y[0]=[3] #修改对象y的值

#再import 整个模块
import module
print(module.y) #可以发现再次被导入的module中的y属性也被修改了,原因是实际上没有重新导入,而是直接调用sys.module中的该模块对象,而该对象的y属性在上述语句中被修改了,因为from只是将module.y的引用复制到主模块的变量y中,其是共享一个对象值的

#out: [3]

搜索路径顺序和相对导入

       import语句导入一个模块,程序搜索的顺序为:1、脚本所在的目录(如果直接运行程序脚本)或工作目录(在交互式环境下);2、PYTHONPATH设置的路径;3、标准库目录;4、.pth配置文件里的目录。这里需要注意的是,要区分主脚本所在的目录和其他程序文件(模块)的目录,这两者可能是不同的;所以对于主模块来说,实际上首先会进行相对导入,但是对于其他非主模块来说,是无法实现相对导入的,只有在包导入的情况下可以实现包内的相对导入。总而言之,只要记住对于模块的导入,其是严格按照上述的顺序进行模块搜索的,而对于包的导入,则可以通过from . import modulename的形式,通过点号进行强制的相对导入,无论是否在主模块中。

模块命名空间的更新原则

这里我们可以通过reload函数进行重新导入,也可以通过删除sys.modules中相应模块后再重新import,但是无论是那种方式进行重新导入,模块的命名空间的更新原则为:保留原属性,增添新属性,更新原属性。即尽管重新导入了,且原来的模块中的属性如果在新导入的模块中被删除了,但更新后的模块对象中依然还有原来的旧属性;如果新模块更新了旧属性或者增添了新的属性,那么这些更新也会在新的模块对象中。这里最重要的一点就是保留了新模块对象中不存在的旧属性,而不是删除。