美文网首页生活不易 我用python
Python-模块导入逻辑详解

Python-模块导入逻辑详解

作者: Woko | 来源:发表于2018-08-06 00:58 被阅读4次

    正式开坑分享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笔记
    突然想起来我还有简书账号,就来这里同步更新一下,欢迎大家来围观~

    相关文章

      网友评论

        本文标题:Python-模块导入逻辑详解

        本文链接:https://www.haomeiwen.com/subject/qsoivftx.html