Python的import
是一个使用非常频繁的操作,这是在一个模块中使用另一个模块的代码的操作(几乎所有的语言都有类似的语句)。import
语句是最常用的方法,但不是唯一的方法,还有importlib.import_module() 和 __import__() 等。本文解释import
语句。
import
做了两件事情:
- 搜索该名字的
module
(其实使用的还是内置的__import__()
方法)然后初始化一个对象
- 将结果与本地的一个变量名绑定(默认就是这个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
文件,然后就可以包名访问包内的名字了。例如下面的文件结构:
|
parent/ __init__.py one/ __init__.py two/ __init__.py three/ __init__.py |
当导入parent.one
时会执行parent/__init__.py
和parent/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 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。
三、加载
加载过程的伪代码如下:
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执行的时候,将和父模块的名字空间绑定。参考下例:
|
spam/ __init__.py foo.py bar.py |
导入foo
将使spam
有spam.foo
属性:
|
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
可以看到一幅漫画。
参考和了解更多
- python文档:https://docs.python.org/3/reference/import.html
- import hook和meta path