Python杂技|Python模块import本质是什么?import是什么

Python杂技|Python模块import本质是什么?import是什么
文章图片

写这篇文章主要是对Python的import模块或包的机制有了更深层级的一个理解,也在具体工作中得到了一点实践,这种思考是由上一篇文章《__main__内置模块预加载Shotgun接口的妙用》触发的。
Python杂技|Python模块import本质是什么?import是什么
文章图片
如果你写过自定义的模块或包,你应该会发现import只会在第一次发生,如果修改代码需要通过reload来强制加载模块,这其中可以理解为Python在import的时候进行了动态加载机制将模块加载到内存当中,我们可以通过sys.modules来查看当前执行环境的内存中已经存在的模块,那如果理解成只要在sys.modules中已经加载过的模块是否就不需要import了呢?我们随便打开一个Python执行环境,你会发现事实并不是这个样子。
Python杂技|Python模块import本质是什么?import是什么
文章图片

【Python杂技|Python模块import本质是什么?import是什么】我们看到上面的os模块在我们的sys.modules中,为何我使用os模块之前还需要import才能使用呢?如果你写过几个py文件构建的包,每个py文件中都要使用到os模块,正常操作在每个py文件中都需要import os代码才能运行,这样每次import不需要消耗内存和时间吗?
我们可以从《Python源码剖析》这本书的模块章节中找到一些答案

从Python的角度看,import其实并不完全等同于我们所熟知的“动态加载”这个概念,它的真实含义是希望某个module能够被感知,即是将这个module以某个符号的形式引入到某个名字空间。如果import等于动态加载,那么Python将对同一个module执行多次动态加载,并且在内存中保存一个module的多个映像,这显然是愚蠢的。
《Python源码剖析》第360页
理解起来有点晦涩,我们看一些代码来验证一下,通过dir()和id()两个内置函数来理解一下他所说的意思
Python杂技|Python模块import本质是什么?import是什么
文章图片

通过交互环境反馈的结果你会惊奇地发现,这里的import os只是起到将os引入到当前的名字空间,通过两个对象在内存中的id可以看到import的os就是已经存在内存中的sys.modules中的os,也就是说这里的import os并没有真的去实例化一个os对象出来,而仅仅是起到引用内存中已存在的os对象。也就是说你在每一个py文件中import os并不会占用更多的内存与导入时间。
那我们想想Python为什么要这样做?既然都是调用sys.modules里的模块,为什么还要通过import来引入名字空间呢?这里要理解名字空间的含义,在我们打开一个Python执行环境的时候,Python就将一大批module加载到内存当中,但为了使local名字空间足够干净,Python没有将这些符号暴露在local名字空间中,而是需要我们显式地通过import机制通知Python,我需要将这个符号引入到local名字空间,以便我的程序能使用这个符号背后的对象。
上面讲的是内置模块引入操作,那么如果是我们自定义的一个模块myModule1会怎样?Python正常地操作流程会是在第一次import myModule1的时候将myModule1加载到sys.modules,也就是内存中,产生pyc文件,并引入当前的名字空间,如果再次import myModule1,它不会再去做动态加载内存的操作,而是直接从内存sys.modules中引入已经加载过的对象,所以就出现了我们需要重新打开执行环境或者reload才能得到我们修改代码的结果。
那么我们再思考一下,比如Houdini中,我如果在启动的时候偷偷将我的模块加载到内存中以达到下次import的时候只是引用它到名字空间以节省第一次加载所带来的时间消耗呢?是不是只要通过123.py先导入这个自定义模块就可以?对的,就是这样的操作,123.py是Houdini的一个启动会执行的脚本代码,具体可上官网查阅用法。

https://www.sidefx.com/docs/houdini/hom/locations.html

Python杂技|Python模块import本质是什么?import是什么
文章图片

再想想,如果我想在myModule1中定义一个全局的变量会怎样?这个全局变量就会随着import myModule1引入名字空间中作为一个全局的实例化对象在Houdini随意调用,这也就是可以通过自定义的一个模块来管理咱们可能需要频繁或者加载时间消耗比较严重的模块或者实例化需要时间的对象,这也就是这篇文章《__main__内置模块预加载Shotgun接口的妙用》另一种最好的解决方案。
# 123.pyimport myModule1# myModule1.pysg = shotgun_api3.Shotgun("https://piedpiper.shotgunstudio.com",login="rhendriks",password="c0mPre$Hi0n")

之后不管是菜单工具,还是工具架工具,还是模块文件中去调用sg接口都可以使用下面的代码引入名字空间操作。
import myModule1myModule1.sg.create() # 使用Shotgun API

    推荐阅读