Python-模块导入逻辑详解

正式开坑分享python啦~

从最开头讲起吧,说一下python里导入一个模块的逻辑

文章目录

  1. 概念明确
  2. 三种模块导入方式
  3. 模块导入详细流程

概念明确

  1. module: 模块,一般指一个.py文件,实际还可能是”.pyo”、”.pyc”、”.pyd”、”.so”、”.dll”
  2. package: 包,指一个包含__inin__.py的目录。(并且这个目录不是__main__所在的目录,因为python解释器不会把当前目录当做package)
  3. <type 'module'>: 每个import过来的module,都是一个module类型的实例,如<module 'sys' (built-in)>
  4. sys.module: 这是一个超全局的dict,里面存的是从开启python解释器以来,所有被import的module,可以理解成一个只存module的全局globals
  5. globles, locals: 类似与当前文件的全局变量合集和局部变量合集,就不详细展开了
  6. 本文使用环境: python2.7,暂不清楚py3会有什么不同

模块引用方式

一共有三个方式引用一个模块:

  1. from path import module
  2. reload(module)
  3. __import__(module, [globals={}, locals={}, fromlist=[], level=-1])

第一种: 直接import

最常见的模块引用方式,不多说了,一般写作import modulefrom path import module

后一种的path,可以是相对路径,这里会引出两个问题来:

  1. 一个.py文件,如果使用了相对路径import,那么这个文件是无法被直接执行的,也就是无法作为__main__来执行,强行执行的话会报错ValueError: Attempted relative import in non-package。原因是,相对路径是使用module.__name__来实现的,直接运行的话,__name__ = '__main__',相对路径找不到原本的package,所以会报错
  2. 如果一个文件使用相对路径引用到了__main__所在的目录,或者更上层目录,那么也会报错,错误信息是ValueError: Attempted relative import beyond toplevel package。原因见package的定义,而且这个报错也很符合直觉

第二种: reload()

重新加载一个模块,一般只会在比较特殊的地方使用

为什么要重新加载呢,因为如果在A里import了B,C里在importA和B的时候,B并没有被重新加载,而是直接把A里的B的内存地址传给了C的本地环境(即直接取了sys.module里的,详见流程介绍),而如果B模块有变化的话,就需要重新加载B

另外一种情况是sys.setdefaultencoding(‘utf8’)这种,这个函数在每次启动python解释器时都会运行,然后就会被删除掉,所以当我们import sys时其实已经没有这个函数了,所以必须reload(sys)一次,才能使得这个函数重新可用。(顺便一提,默认sys.getdefaultencoding()是ascii)

另外,只有之前import过的module,才能reload,原因见下面reload流程解释

第三种: __import__()

第一种方法实际上就是调用的这个函数,它接收字符串作为参数,我们一般不直接用,但在讲反射的文章里100%会用这个函数举例子

语法就不细讲了,真用到的时候再去看手册也来得及,一般用途是动态载入模块(如web api框架),或延迟模块载入(如放到__getattr__里),以后可以写一写

模块引用详细流程

import流程

  1. 在sys.module里找模块名,找到了就将其引用加入本地locals里
  2. 如果没有找到,则从sys.path里按顺序查找模块文件,找到后先将其名字添加到sys.module,此时这个module的__dict__是空的
  3. 然后执行一遍该文件,并将执行过后的locals填充到sys.module的相应key下。这里可能会有个循环import的坑,下面再说
  4. 填充完sys.module后,将其引用加入本地locals,如第一步

这可以解释上文留下的一个问题“为什么需要reload”,因为import的时候,只是从sys.module里找出引用写到当前locals里,并不会重新执行一遍原模块

循环导入的坑是咋回事:
直接贴一个别人的链接吧,他那边图文并茂比我讲的好多了: https://www.jb51.net/article/51815.htms

reload流程

  1. 在sys.module里找到这个模块,找不到会报错ImportError: reload(): module skrskr not in sys.modules
  2. 执行一遍对应文件,用执行过程中得到的locals里的属性替换掉原有模块对象的相应属性,注意是替换模块内的属性,这个模块对象本身的内存地址并没有发送变化,即reload前后,他的id并没有发生变化

这也可以顺便解释上文留下的第二个问题“为什么必须先import才能reload”,因为是在sys.module里直接取的对象,再进行的下一步操作。这里说的先import,不一定是在当前文件import的,比如下面的代码是不会报错的,因为os在启动python时已经默认加载进去了

import sys
reload(sys.modules['os'])

模块加载流程

是指生成这个模块对象并塞到sys.module里的过程,因为没什么坑,不太容易影响到正常工作,所以我还没有看…

但是ModuleFinder类的源码是在这个位置,有兴趣的可以研究下它到底是怎么回事: lib/python2.7/modulefinder.py:75#ModuleFinder

文章首发于微信公众号:Woko笔记
突然想起来我还有简书账号,就来这里同步更新一下,欢迎大家来围观~

    原文作者:Woko
    原文地址: https://www.jianshu.com/p/4196e2eb647e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞