惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Recent Announcements
Recent Announcements
B
Blog
D
Docker
V
V2EX
GbyAI
GbyAI
L
LangChain Blog
博客园 - Franky
U
Unit 42
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
博客园_首页
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
量子位
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点
理解Python的import
laixintao · 2017-10-13 · via 卡瓦邦噶!

Python的import是一个使用非常频繁的操作,这是在一个模块中使用另一个模块的代码的操作(几乎所有的语言都有类似的语句)。import语句是最常用的方法,但不是唯一的方法,还有importlib.import_module() 和 __import__() 等。本文解释import语句。

import做了两件事情:

  1. 搜索该名字的module(其实使用的还是内置的__import__()方法)然后初始化一个对象
  2. 将结果与本地的一个变量名绑定(默认就是这个module的名字)。

意味着,这两句是等价的:import package as pk 和 pg = __import__('package')

值得注意的是,只调用__import__()只会执行搜索动作,初始化module对象,最后丢掉该对象(因为没有赋值操作)。这点很容易将内置的__imoprt__()方法和import语句搞混。

一、Packages

Python中只有一种module类型(type),所有的module,无论是C语言实现的,还是Python实现的,还是别的,都是这种了类型。为了使名字空间有等级,Python又有packages的概念。注意这里只是一个“概念”而并不是类型,packages只是一种特殊的“module”。官方文档中将packages比作文件系统的文件夹,module比作文件(但又不完全是)。所有的package都是module,而并不是所有的module都是package。

Python的package又有两种:

Regular package

Regular package就是一个包含 __init__.py文件的文件夹。当此包导入的时候,会执行这个__init__.py文件,然后就可以包名访问包内的名字了。例如下面的文件结构:

1

2

3

4

5

6

7

8

parent/

    __init__.py

    one/

        __init__.py

    two/

        __init__.py

    three/

        __init__.py

当导入parent.one时会执行parent/__init__.pyparent/one/__init__.py

Namespace package

这是PEP 420提出的。

Namespace package由各种不同的部分(portions)组成。portion可以是zip文件,可以使网络上的资源,可以是文件系统中某个位置的资源。Namespace package在文件系统中可能没有实际的文件来表示,可能是虚拟的module。

# TODO read 420 理解namespace

下面来讲import的搜索顺序。

二、搜索

开始搜索之前,需要一个“合适的名字”(来自import语法或者__import__()函数等),即一个通过点分隔的路径名。比如foo.bar.baz,Python会先导入foo,然后导入foo.bar,然后导入foo.bar.baz,如果中间有任何失败,就会抛出ModuleNotFoundError

缓存(sys.modules)

import首先搜索的地方是sys.modules,此为一个键值对的mapping,作为一个缓存,保存了中间形式的路径。所以如果之前import了 foo.bar.baz,那么sys.modules中就会存在foo, foo.bar, foo.bar.baz。每一个名字都对应一个module对象。

import的时候,如果在sys.modules中找到就会返回这个module对象,import过程结束。如果根据key找到了,但是value是None,就会抛出ModuleNotFoundError,如果key不存在,就继续搜索过程。

sys.modules是可写的,删除任何key并不会销毁对象(因为可能别的模块引用了这个模块),但是你可以将value设置为None强制抛出ModuleNotFoundError。下面的代码展示了这个操作(注意使用ipython结果不同,ipython在启动的时候import 了re库)。

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Python 3.6.2 (default, Jul 17 2017, 16:44:45)

[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

>>> import sys

>>> sys.modules['re']

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

KeyError: 're'

>>> import re

>>> sys.modules['re']

<module 're' from '/Users/laixintao/.virtualenvs/parallel/lib/python3.6/re.py'>

>>> sys.modules['re'] = None

>>> import re

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ModuleNotFoundError: import of 're' halted; None in sys.modules

要注意的是,如果你有module对象的引用(在sys.module的缓存),但是重新import了module对象,这两个module不会一致。也就是说你再引用一遍,代码中的该module还是从sys.module中的,所以你拿不到新的module。但是使用importlib.reload() 就可以将改module对象重新初始化一遍。这在REPL中测试代码的时候比较实用。

Finder和Loader

如果在缓存(sys.module)中没有找到的话,python就开始import机制来导入这个包。Python的import协议包括两部分:Finder和Loader。只要实现了这两部分,就算是一个importer,importer如果发现自己可以导入目标包的话,就会返回他们自己。顾名思义,Finder的任务是定位包,并不做载入的部分,Loader做真正载入包的工作。Python有三个默认的importer:一个可以导入build-in module,第二个可以导入frozen module, 第三个可以搜索import path。

三、加载

加载过程的伪代码如下:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

module = None

if spec.loader is not None and hasattr(spec.loader, 'create_module'):

    # It is assumed 'exec_module' will also be defined on the loader.

    module = spec.loader.create_module(spec)

if module is None:

    module = ModuleType(spec.name)

# The import-related module attributes get set here:

_init_module_attrs(spec, module)

if spec.loader is None:

    if spec.submodule_search_locations is not None:

        # namespace package

        sys.modules[spec.name] = module

    else:

        # unsupported

        raise ImportError

elif not hasattr(spec.loader, 'exec_module'):

    module = spec.loader.load_module(spec.name)

    # Set __loader__ and __package__ if missing.

else:

    sys.modules[spec.name] = module

    try:

        spec.loader.exec_module(module)

    except BaseException:

        try:

            del sys.modules[spec.name]

        except KeyError:

            pass

        raise

return sys.modules[spec.name]

注意一下细节:

  • sys.module已经存在目标模块的名字,import直接返回此模块
  • 在Loader执行模块代码之前,先将其放入到sys.module。这一点非常重要,可以防止循环导入(模块可能在代码中导入该模块自己)。
  • 如果loading失败,失败的模块(只有失败的模块)会从sys.module删除
  • 在module创建但是执行代码之前,import会设置 import-related 模块属性,作为后续的总结
  • 模块执行的部分才是模块的名字空间真正暴露的时候,这部分完全由loader处理。
  • loading的时候创建的模块、传入exec_module()的模块可能并不是最后返回的模块

Loader

Loader提供了loading最重要的部分:模块的执行。import机制会调用importlib.abc.Loader.exec_module(),参数只有一个,要执行的module对象。其返回值被丢弃。Loader必须满足两个要求:

  • module必须是一个Python的module对象,Loader必须在module的全局名字空间下(module.__dict__)执行module的代码
  • 如果不能执行代码,必须抛出ImportError,其余所有异常都会被忽略

子模块

当子模块的loading执行的时候,将和父模块的名字空间绑定。参考下例:

Python

1

2

3

4

spam/

    __init__.py

    foo.py

    bar.py

导入foo将使spamspam.foo属性:

Python

1

2

3

4

5

6

7

8

from .foo import Foo

from .bar import Bar

>>> import spam

>>> spam.foo

<module 'spam.foo' from '/tmp/imports/spam/foo.py'>

>>> spam.bar

<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

就先写到这里,其实还有一个比较重要的话题就是模块的搜索顺序,以及子模块之间如何组织。新手尝尝遇到在子模块中遇到不能import其他模块的问题,以后再写吧。

彩蛋

import this可以看到Zen of Python

import antigravity可以看到一幅漫画

参考和了解更多

  1. python文档:https://docs.python.org/3/reference/import.html
  2. import hook和meta path