Python 模块加载

作者: 超net | 来源:发表于2015-10-20 15:44 被阅读1149次

    《Python 源码剖析》笔记

    import

    在交互环境下,使用不带参数的dir()可以打印当前local命名空间的所有

    >>> locals()
    {'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__']
    

    通过importlocal添加sys模块

    >>> import sys
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys']
    >>> locals()
    {'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'sys': <module 'sys' (built-in)>, '__doc__': None, '__package__': None}
    

    可以看到,import机制使得加载的模块在local命名空间可见

    在 Python 初始化时,就有大批 module 已经加载到内存中,但为了保持local空间的干净,并没有直接将这些 module 放入local空间,而是让用户通过import机制通知 Python:我的程序需要调用某个模块,请将它放入local命名空间。

    那些被预加载到内存的模块,存放在 全局module集合sys.modules中(下面列出了部分)。此时虽然sys.modules中能看到os,但调用失败,因为还没有放入local空间

    >>> sys.modules
    {'sys': <module 'sys' (built-in)>, 'os.path': <module 'posixpath' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>, 'os': <module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>}
    >>> os
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'os' is not defined
    

    通过import os,将模块放入local空间,用于调用

    >>> import os
    >>> os
    <module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']
    >>> locals()
    {'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'sys': <module 'sys' (built-in)>, '__name__': '__main__', 'os': <module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>, '__doc__': None}
    

    那么,对于那些一开始就并不在sys.modules中的模块,比如用户自定义的模块,又会如何呢?

    准备了一个简单的module——hello.py:

    a = 1
    b = 2
    

    hello.py进行import操作,结果,将'hello'加载进了sys.modules,同时放入了local空间

    >>> sys.modules['hello']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'hello'
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']
    >>> import hello
    >>> sys.modules['hello']
    <module 'hello' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hello.py'>
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'hello', 'os', 'sys']
    

    通过id模块,查看这两个对象指向了同一个内存地址空间,是同一个模块对象。

    >>> id(hello)
    4300177880
    >>> id(sys.modules['hello'])
    4300177880
    

    可以看到,module对象其实就是通过一个dict维护所有的属性和值。也就是说,是一个名字空间。

    >>> dir(hello)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']
    >>> hello.__dict__.keys()
    ['a', 'b', '__builtins__', '__file__', '__package__', '__name__', '__doc__']
    >>> hello.__name__
    'hello'
    >>> hello.__file__
    '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hello.py'
    >>> hello.__package__
    >>> hello.__doc__
    >>> hello.a
    1
    >>> hello.b
    2
    

    嵌套import

    那么,对于嵌套的module呢?

    hi1.py

    import hi2
    

    hi2.py

    import sys
    

    结果可以看到,在local空间中也是嵌套的

    >>> import hi1
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'hello', 'hi1', 'os', 'sys']
    >>> dir(hi1)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'hi2']
    >>> dir(hi2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'hi2' is not defined
    >>> dir(hi1.hi2)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'sys']
    

    这些模块也同时出现在了sys.modules中,这样,当其他地方也要调用这些模块时,可以直接返回其中的 module对象。

    >>> sys.modules['hi1']
    <module 'hi1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hi1.py'>
    >>> sys.modules['hi2']
    <module 'hi2' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hi2.py'>
    >>> sys.modules['sys']
    <module 'sys' (built-in)>
    

    import package

    如同一些相关的 class 可以放入一个 module 中,相关的 module 也可以放入一个 package,用于管理 module。当然,多个小 package 也可以组成一个较大的 package。

    ➜  task  pwd
    /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task
    ➜  task  ls
    tank1.py tank2.py
    
    >>> import task
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: No module named task
    

    package 就是一个文件夹,但并非所有文件夹都是package,只有在文件夹中有一个特殊文件__init__.py时,Python 才会认为是合法的,即使这个文件是空的。

    ➜  task  ls
    __init__.py tank1.py    tank2.py
    

    再次导入 package,自动加载执行__init__.py,生成字节码__init__.pyc

    >>> import task
    
    ➜  task  ls
    __init__.py  __init__.pyc tank1.py     tank2.py
    

    导入 package 中的 module

    >>> import task.tank1
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', '__warningregistry__', 'hello', 'hi1', 'os', 'sys', 'task']
    >>> dir(task)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'tank1']
    >>> sys.modules['task']
    <module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.py'>
    >>> sys.modules['tank1']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'tank1'
    >>> sys.modules['task.tank1']
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>
    

    可以看到,module 和 package 的导入基本一样。只不过包本身也被加载进来,而且在sys.modules中,模块名称中包含包名,这是为了区别不同包中的同名模块,以便共存。

    为什么task也会被加载呢,似乎用户并不需要?这是因为对tank1的引用需要通过task.tank1实现,Python 会首先在local空间中查找task,然后在task的属性集合中查找tank1

    import task.tank1时,只会导入tank1,不会同时导入tank2

    而且,packagetask只会加载一次。

    在导入类似A.B.C的结构时,Python 会视为树形结构,B在A下,C在B下,Python 会对这个结构进行分解,形成(A,B,C)的节点集合。从左到右依次到sys.modules中查找是否已经加载,如果是一个 package,A已经加载,但 B 和 C 还没有,就到 A 对应的 模块对象 的__path__中查找路径,并只在路径中搜索。

    from import

    我们可以只将 package 中的某个 module 甚至 某个 module 中的某个符号加载到内存,比如上例中,我们希望直接将tank1引入local空间,不需要引入task

    >>> import sys
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys']
    >>> from task import tank1
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'tank1']
    >>> sys.modules['task']
    <module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.pyc'>
    >>> sys.modules['task.tank1']
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
    

    可以看到,虽然local中只有tank1,但sys.modules依然加载了tasktask.tank1,所以,import 和 from import 本质是一样的,需要将 包 和 模块 同时加载到sys.modules,区别只是加载完成后,将什么名称放入local空间。

    import task.tank1中,引入了task并映射到module task,而在from task import tank1中,引入了tank1并映射到module task.tank1

    可以只引入模块中的一些对象

    tank1.py

    a = 1
    
    b = 2
    
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys']
    >>> from task.tank1 import a
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'a', 'sys']
    >>> a
    1
    >>> sys.modules['task']
    <module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.pyc'>
    >>> sys.modules['tank1']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'tank1'
    >>> sys.modules['task.tank1']
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>
    >>> sys.modules['task.tank1.a']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'task.tank1.a'
    

    也可以一次性导入 module 中的所有对象

    >>> from task.tank1 import *
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'a', 'b', 'sys']
    

    import as & from import as

    Python 还允许自己重命名被引入local的模块

    >>> import task.tank1 as test
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'test']
    >>> sys.modules['test']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'test'
    >>> sys.modules['task.tank1']
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
    

    这时,test映射到了module task.tank1

    模块的销毁

    如果一个模块不需要了,可以通过del销毁,但这样这的是销毁了这个模块对象么?

    >>> del test
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys']
    >>> sys.modules['task.tank1']
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
    

    可以看出,del 只是简单地将testlocal中删除,并没有从 module集合 中销毁。但这样已经能够让 Python程序无法访问这个模块,认为test不存在了。

    为什么 Python 不直接将模块从sys.modules删除?因为一个系统的多个Python文件可能都会对某个module进行 import,而 import 的作用并非一直以为的加载,而是让某个module能够被感知,即以某种符号的形式引入某个名字空间。所以,使用全局的sys.modules保存module的唯一映射,如果某个Python文件希望感知到,而sys.modules中已经加载,就将这个模块名称引入该Python文件的名称空间,然后关联到sys.modules中的该模块,如果sys.modules中不存在,才会进行加载。

    模块重新载入

    那么,对于sys.modules中已经加载到内存的模块,如果后来对内容进行了修改,怎么让Python知道呢?有一种重新加载的机制。

    >>> import task.tank1 as test
    >>> dir()
    ['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'test']
    >>> dir(test)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']
    >>> test.a
    1
    >>> test.b
    1
    

    tank1.py 添加 整数对象 c

    a = 1
    
    b = 1
    
    c = 3
    

    重新加载,C出现了

    >>> reload test
      File "<stdin>", line 1
        reload test
                  ^
    SyntaxError: invalid syntax
    >>> reload(test)
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>
    >>> dir(test)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b', 'c']
    >>> test.a
    1
    >>> test.b
    1
    >>> test.c
    3
    >>> id(test)
    4300178104
    >>> reload(test)
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
    >>> id(test)
    4300178104
    

    id(test)可以看出,依然是原来的对象,Python只是在原有对象中添加了C和对应的值。

    但是,删除b = 1后,还可以调用,说明Python的重新加载只是向module添加新对象,而不管是否已经删除。

    可以通过用del直接删除的方式进行更新

    >>> del test.b
    >>> dir(test)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'c']
    >>> reload(test)
    <module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
    >>> dir(test)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'c']
    

    相关文章

      网友评论

        本文标题:Python 模块加载

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