Python 中 import 的机制与实现

2023-10-27

转自:http://python.jobbole.com/82604/、

本文所涉及到的代码在github上。

概述

Python 是一门优美简单、功能强大的动态语言。在刚刚接触这门语言时,我们会被其优美的格式、简洁的语法和无穷无尽的类库所震撼。在真正的将python应用到实际的项目中,你会遇到一些无法避免的问题。最让人困惑不解的问题有二类,一个 编码问题,另一个则是引用问题。

本文主要讨论关于Python中import的机制与实现、以及介绍一些有意思的Python Hooks。

Python 类库引入机制

首先,看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
"" "
目录结构如下:
├── __init__.py
├── main.py
└── string.py
" ""
# main.py 内容如下
import string
print string . a
# string.py 内容如下
a = 2

现在,考虑一下:

  1. 当我们执行main.py的时候,会发生什么事情?
  2. 在main.py文件执行到 import string 的时候,解释器导入的string类库是当前文件夹下的string.py还是系统标准库的string.py呢?
  3. 如果明确的指明⾃己要引⼊的类库?

为了搞清楚上面的问题,我们需要了解关于Python类库引入的机制。

Python的两种引入机制

Python 提供了二种引入机制:

  1. relative import
  2. absolute import

relative import

relative import 也叫作相对引入,在Python2.5及之前是默认的引入方法。它的使用方法如下:

Python
1
2
3
from . string import a
from . . string import a
from . . . string import a

这种引入方式使用一个点号来标识引入类库的精确位置。与linux的相对路径表示相似,一个点表示当前目录,每多一个点号则代表向上一层目录。

1
2
3
4
5
6
7
8
9
10
11
"" "
├── __init__.py
├── foo.py
└── main.py
" ""
# foo.py
a = 2
# main.py
print __name__
from . foo import a
print a

相对引入,那么我们需要知道相对什么来引入。相对引入使用被引入文件的 __name__ 属性来决定该文件在整个包结构的位置。那么如果文件的__name__没有包含任何包的信息,例如__name__ 被设置为了__main__,则认为其为‘top level script’,而不管该文件的位置,这个时候相对引入就没有引入的参考物。如上面的程序所示,当我们执行 pythonmain.py 时,Python解释器会抛出 ValueError: Attempted relative import in non-package的异常。

为了解决这个问题,PEP 0366 — Main module explicit relative imports提出了一个解决方案。允许用户使用python -m ex2.main的方式,来执行该文件。在这个方案下,引入了一个新的属性__package__

1
2
3
4
5
6
7
8
9
10
11
liuchang @ localhost    ~ / Codes / pycon
$ cat ex2 / main . py
print __name__
print __package__
from . foo import a
print a
liuchang @ localhost    ~ / Codes / pycon
$ python - m ex2 . main
__main__
ex2
2

absolute import

absolute import 也叫作完全引入,非常类似于Java的引入进制,在Python2.5被完全实现,但是是需要通过 from __future__ import absolute_import 来打开该引入进制。在Python2.6之后以及Python3,完全引用成为Python的默认的引入机制。它的使用方法如下:

Python
1
2
from pkg import foo
from pkg . moduleA import foo

要注意的是,需要从包目录最顶层目录依次写下,而不能从中间开始。

在使用该引入方式时,我们碰到比较多的问题就是因为位置原因,Python找不到相应的库文件,抛出ImportError的异常。让我们看一个完全引用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"" "
ex3
├── __init__.py
├── foo.py
└── main.py
" ""
# foo.py
a = 2
 
# main.py
print __name__
print __package__
from ex2 . foo import a
print a

我们尝试着去运行main.py文件,Python解释器会抛出ImportError。那么我们如何解决这个问题呢?

1
2
3
4
5
6
7
$ python ex3 / main . py
__main__
None
Traceback ( most recent call last ) :
   File "ex3/main.py" , line 3 , in < module >
     from ex2 . foo import a
ImportError : No module named ex2 . foo

首先,我们也可以使用前文所述的module的方式去运行程序,通过-m参数来告诉解释器 __package__ 属性。如下:

Python
1
2
3
4
5
liuchang @ liuchangdeMacBook - Pro    ~ / Codes / pycon
$ python - m ex3 . main                                                                            
__main__
ex3
2

另外,我们还有一个办法可以解决该问题,在描述之前,我们介绍一个关于Python的非常有用的小知识:Python解释器会自动将当前工作目录添加到PYTHONPATH。如下所示,可以看到我们打印出的 sys.path 已经包含了当前工作目录。

1
2
3
4
5
6
7
liuchang @ liuchangdeMacBook - Pro    ~ / Codes / pycon / ex4
$ cat main . py
import sys
print sys . path
liuchang @ liuchangdeMacBook - Pro    ~ / Codes / pycon / ex4
$ python main . py
[ '/Users/liuchang/Codes/pycon/ex4' , '/Library/Python/2.7/site-packages/pip-7.1.0-py2.7.egg' , '/Library/Python/2.7/site-packages/mesos-_PACKAGE_VERSION_-py2.7.egg' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages' , '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old' , '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload' , '/Users/liuchang/Library/Python/2.7/lib/python/site-packages' , '/usr/local/lib/python2.7/site-packages' , '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC' , '/Library/Python/2.7/site-packages' ]

了解了Python解释器的这个特性后,我们就可以解决完全引用的找不到类库的问题:执行的时候,让解释器自动的将类库的目录添加到PYTHONPATH中。

我们可以在顶层目录中添加一个run_ex3.py的文件,文件内容和运行结果如下,可以看到Python解释器正确的执行了ex3.main文件。

1
2
3
4
5
6
7
8
liuchang @ liuchangdeMacBook - Pro    ~ / Codes / pycon
$ cat run_ex3 . py
from ex3 import main
liuchang @ liuchangdeMacBook - Pro    ~ / Codes / pycon
$ python run_ex3 . py
ex3 . main
None
2

一些实践经验

相对引用还是绝对引用?

上面介绍了Python的两种引用方式,都可以解决引入歧义的问题。那我们应该使用哪一种呢?

先说明一下Python的默认引用方式,在Python2.4及之前,Python只有相对引用这一种方式,在Python2.5中实现了绝对引用,但默认没有打开,需要用户自己指定使用该引用方式。在之后的版本和Python3版本,绝对引用已经成为默认的引用方式。

其次,二种引用方式各有利弊。绝对引用代码更加清晰明了,可以清楚的看到引入的包名和层次,但是,当包名修改的时候,我们需要手动修改所有的引用代码。相对引用则比较精简,不会被包名修改所影响,但是可读性较差,不如完全引用清晰。

最后,对于两种引用的方式选择,还是有争论的。在PEP8中,Python官方推荐的是绝对引用,详细理由可以参考这儿

Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured (such as when a directory inside a package ends up on sys.path ):

Python
1
2
3
import mypkg . sibling
from mypkg import sibling
from mypkg . sibling import example

However, explicit relative imports are an acceptable alternative to absolute imports, especially when dealing with complex package layouts where using absolute imports would be unnecessarily verbose:

Python
1
2
from . import sibling
from . sibling import example

Standard library code should avoid complex package layouts and always use absolute imports. Implicit relative imports should never be used and have been removed in Python 3.

规范打包发布

为了别人使用自己代码的方便,应该尽量使用规范的包分发机制。为自己的Python包编写正确的setup.py文件,添加相应的README.md文件。对于提供一些可执行命令的包,则可以使用 console_entrypoint 的机制来提供。因为打包和分发不是本文重点,不再详细叙述,大家可以查看官方文档。

使用virtualenv管理包依赖

在使用Python的时候,尽量使用virtualenv来管理项目,所有的项目从编写到运行都在特定的virtualenv中。并且为自己的项目生成正确的依赖描述文件。

1
pip freeze > requirements . txt

关于virtualenv的用法,可以参考我之前的一篇文章virtualenv教程

Python import实现

Python 提供了 import 语句来实现类库的引用,下面我们详细介绍当执行了 import 语句的时候,内部究竟做了些什么事情。

当我们执行一行  from package import module as mymodule 命令时,Python解释器会查找package这个包的module模块,并将该模块作为mymodule引入到当前的工作空间。所以import语句主要是做了二件事:

  1. 查找相应的module
  2. 加载module到local namespace

下面我们详细了解python是如何查找模块的。

查找module的过程

在import的第一个阶段,主要是完成了查找要引入模块的功能,这个查找的过程如下:

  1. 检查 sys.modules (保存了之前import的类库的缓存),如果module被找到,则⾛到第二步。
  2. 检查 sys.meta_path。meta_path 是一个 list,⾥面保存着一些 finder 对象,如果找到该module的话,就会返回一个finder对象。
  3. 检查⼀些隐式的finder对象,不同的python实现有不同的隐式finder,但是都会有 sys.path_hooks, sys.path_importer_cache 以及sys.path。
  4. 抛出 ImportError。

sys.modules

对于第一步中sys.modules,我们可以打开Python来实际的查看一下其内容:

1
2
3
4
5
6
7
8
Python 2.7.10 ( default , Aug 22 2015 , 20 : 33 : 39 )
[ GCC 4.2.1 Compatible Apple LLVM 7.0.0 ( clang - 700.0.59.1 ) ] on darwin
Type "help" , "copyright" , "credits" or "license" for more information .
>> import sys
>> sys . modules
{ 'copy_reg' : < module 'copy_reg' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy_reg.pyc' > , 'sre_compile' : < module 'sre_compile' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_compile.pyc' > , '_sre' : < module '_sre' ( built - in ) > , 'encodings' : < module 'encodings' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.pyc' > , 'site' : < module 'site' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc' > , '__builtin__' : < module '__builtin__' ( built - in ) > , 'sysconfig' : < module 'sysconfig' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sysconfig.pyc' > , 'encodings.encodings' : None , '__main__' : < module '__main__' ( built - in ) > , 'supervisor' : < module 'supervisor' ( built - in ) > , 'abc' : < module 'abc' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/abc.pyc' > , 'posixpath' : < module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc' > , '_weakrefset' : < module '_weakrefset' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_weakrefset.pyc' > , 'errno' : < module 'errno' ( built - in ) > , 'encodings.codecs' : None , 'sre_constants' : < module 'sre_constants' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_constants.pyc' > , 're' : < module 're' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.pyc' > , '_abcoll' : < module '_abcoll' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_abcoll.pyc' > , 'types' : < module 'types' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/types.pyc' > , '_codecs' : < module '_codecs' ( built - in ) > , 'encodings.__builtin__' : None , '_warnings' : < module '_warnings' ( built - in ) > , 'genericpath' : < module 'genericpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/genericpath.pyc' > , 'stat' : < module 'stat' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/stat.pyc' > , 'zipimport' : < module 'zipimport' ( built - in ) > , '_sysconfigdata' : < module '_sysconfigdata' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_sysconfigdata.pyc' > , 'mpl_toolkits' : < module 'mpl_toolkits' ( built - in ) > , 'warnings' : < module 'warnings' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/warnings.pyc' > , 'UserDict' : < module 'UserDict' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.pyc' > , 'encodings.utf_8' : < module 'encodings.utf_8' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.pyc' > , 'sys' : < module 'sys' ( built - in ) > , '_osx_support' : < module '_osx_support' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_osx_support.pyc' > , 'codecs' : < module 'codecs' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.pyc' > , 'readline' : < module 'readline' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/readline.so' > , 'os.path' : < module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc' > , '_locale' : < module '_locale' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/_locale.so' > , 'signal' : < module 'signal' ( built - in ) > , 'traceback' : < module 'traceback' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/traceback.pyc' > , 'linecache' : < module 'linecache' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/linecache.pyc' > , 'posix' : < module 'posix' ( built - in ) > , 'encodings.aliases' : < module 'encodings.aliases' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/aliases.pyc' > , 'exceptions' : < module 'exceptions' ( built - in ) > , 'sre_parse' : < module 'sre_parse' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_parse.pyc' > , 'os' : < module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc' > , '_weakref' : < module '_weakref' ( built - in ) > }
>> sys . modules [ 'zlib' ] . __file_ _
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/zlib.so'

可以看到sys.modules已经保存了一些包的信息,由这些信息,我们就可以直接知道要查找的包的位置等信息。

finder、loader和importer

在上文中,我们提到了sys.meta_path中保证了一些finder对象。在python中,不仅定义了finder的概念,还定义了loader和importor的概念。

  • finder的任务是决定自己是否根据名字找到相应的模块,在py2中,finder对象必须实现find_module()方法,在py3中必须要实现find_module()或者find_loader()方法。如果finder可以查找到模块,则会返回一个loader对象(在py3.4中,修改为返回一个module specs)。
  • loader则是负责加载模块,它必须实现一个load_module()的方法。
  • importer 则指一个对象,实现了finder和loader的方法。因为Python是duck type,只要实现了方法,就可以认为是该类。

sys.meta_path

在Python查找的时候,如果在sys.modules没有查找到,就会依次调用sys.meta_path中的finder对象。默认的情况下,sys.meta_path是一个空列表,并没有任何finder对象。

Python
1
2
In [ 6 ] : sys . meta_path
Out [ 6 ] : [ ]

我们可以向sys.meta_path中添加一些定义的finder,来实现对Python加载模块的修改。比如下例,我们实现了一个会将每次加载包的信息打印出来的finder。

Python
1
2
3
4
5
6
7
8
9
10
11
12
from __future__ import print_function
import sys
 
class Watcher ( object ) :
     @ classmethod
     def find_module ( cls , name , path , target = None ) :
         print ( "Importing" , name , path , target )
         return None
 
sys . meta_path . insert ( 0 , Watcher )
 
import socket

当我们执行的时候,就可以看到系统加载socket包时所发生的事情。

Python
1
2
3
4
5
6
7
8
liuchang @ localhost    ~ / Codes / pycon / ex5_meta _path
$ python finder1 . py
Importing socket None None
Importing _socket None None
Importing functools None None
Importing _functools None None
Importing _ssl None None
Importing cStringIO None None

sys.path hook

Python import的hook分为二类,一类是上一章节已经描述的meta hook,另一类是 path hook。

当处理sys.path(或者package.path)时,就会调用对应的一部分的 Pack hook。Path Hook是通过向sys.path_hooks 中添加一个importer生成器来注册的。

sys.path_hooks 是由可被调用的对象组成,它会顺序的检查以决定他们是否可以处理给定的sys.path的一项。每个对象会使用sys.path项的路径来作为参数被调用。如果它不能处理该路径,就必须抛出ImportError,如果可以,则会返回一个importer对象。之后,不会再尝试其它的sys.path_hooks对象,即使前一个importer出错了。

详细可以参考registering-hooks

python import hooks

在介绍完Python的引用机制与一些实现方法后,接下来我们介绍一些关于如何根据自己的需求来扩展Python的引用机制。

在开始详细介绍前,给大家展示一个实用性不高,但是很有意思的例子:让Python在执行代码的时候自动安装缺失的类库。我们会实现一个autoinstall的模块,只要import了该模块,就可以打开该功能。如下所示,我们尝试引入tornado库的时候,iPython会提示我们没有安装。然后,我们引入了autoinstall,再尝试引入tornado,iPython就会自动的安装tornado库。

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
In [ 1 ] : import tornado
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
ImportError                                Traceback ( most recent call last )
< ipython - input - 1 - 3eac10687b7e > in < module > ( )
-- -- > 1 import tornado
 
ImportError : No module named tornado
 
In [ 2 ] : import autoinstall
 
In [ 3 ] : import tornado
Installing tornado
 
Collecting tornado
   Downloading tornado - 4.2.1.tar.gz ( 434kB )
Collecting backports . ssl - match - hostname ( from tornado )
   Downloading http : / / 182.92.2.186 : 7002 / packages / backports . ssl_match_hostname - 3.4.0.2 - py2 - none - any . whl
Collecting certifi ( from tornado )
   Downloading certifi - 2015.9.6.2 - py2 . py3 - none - any . whl ( 371kB )
Installing collected packages : backports . ssl - match - hostname , certifi , tornado
   Running setup . py install for tornado
Successfully installed backports . ssl - match - hostname - 3.4.0.2 certifi - 2015.9.6.2 tornado - 4.2.1

这个功能的实现其实很简单,利用了sys.meta_path。autoinstall的全部代码如下:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from __future__ import print_function
import sys
import subprocess
 
class AutoInstall ( object ) :
     _loaded = set ( )
 
     @ classmethod
     def find_module ( cls , name , path , target = None ) :
         if path is None and name not in cls . _loaded :
             cls . _loaded . add ( name )
             print ( "Installing" , name )
             try :
                 out = subprocess . check_output ( [ 'sudo' , sys . executable , '-m' , 'pip' , 'install' , name ] )
                 print ( out )
             except Exception as e :
                 print ( "Failed" + e . message )
         return None
 
sys . meta_path . append ( AutoInstall )

import hook的重要性

我们为什么需要Python import的hook呢?使用import的hook可以让我们做到很多事情,比如说当我们的Python包存储在一个非标准的文件中,或者Python程序存储在网络数据库中,或者像py2exe一样将Python程序打包成了一个文件,我们需要一种方法来正确的解析它们。

其次,我们希望在Python加载类库的时候,可以额外的做一些事情,比如上传审计信息,比如延迟加载,比如自动解决上例的依赖未安装的问题。

所以,import系统的Hook技术是值的花时间学习的。

如何实现import hooks

Python提供了一些方法,让我们可以在代码中动态的调用import。主要有如下几种:

  1. __import__ : Python的内置函数
  2. imputil : Python的import工具库,在py2.6被声明废弃,py3中彻底移除。
  3. imp : Python2 的一个import库,py3中移除
  4. importlib : Python3 中最新添加,backport到py2.7,但只有很小的子集(只有一个函数)。

Python2 所有关于import的库的列表参见Importing Modules。Python3 的可以参考Importing Modules PEP 0302 — New Import Hooks 提案详细的描述了importlib的目的、用法。

一些Hook示例

Lazy化库引入

使用Import Hook,我们可以达到Lazy Import的效果,当我们执行import的时候,实际上并没引入该库,只有真正的使用这个库的时候,才会将其引入到当前工作空间。 具体的代码可以参考github。 实现的效果如下:

Python
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python
 
import limp    # Lazy imports begin now
 
import json
import sys
 
print ( 'json' in sys . modules )    # False
print ( ', ' . join ( json . loads ( '["Hello", "World!"]' ) ) )
print ( 'json' in sys . modules )    # True

它的实现也很简单:

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
import sys
import imp
 
_lazy_modules = { }
 
class LazyModule ( ) :
     def __init__ ( self , name ) :
         self . name = name
 
     def __getattr__ ( self , attr ) :
         path = _lazy_modules [ self . name ]
         f , pathname , desc = imp . find_module ( self . name , path )
 
         lf = sys . meta_path . pop ( )
         imp . load_module ( self . name , f , pathname , desc )
         sys . meta_path . append ( lf )
 
         self . __dict__ = sys . modules [ self . name ] . __dict__
         return self . __dict__ [ attr ]
 
class LazyFinder ( object ) :
 
     def find_module ( self , name , path ) :
         _lazy_modules [ name ] = path
         return self
 
     def load_module ( self , name ) :
         return LazyModule ( name )
 
sys . meta_path . append ( LazyFinder ( ) )

Flask 插件库统一入口

使用过Flask的同学都知道,Flask的对于插件提供了统一的入口。比如说我们安装了Flask_API这个库,然后我们可以直接 import flask_api 来使用这个库,同时Flask还允许我们采用 import flask.ext.api 的方式来引用该库。

这里Flask就是使用了import 的hook,当引入flask.ext的包时,就自动的引用相应的库。Flask实现了一个叫ExtensionImporter的类,这个类实现了find_module和load_module代码实现如下github

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class ExtensionImporter ( object ) :
     """This importer redirects imports from this submodule to other locations.
    This makes it possible to transition from the old flaskext.name to the
    newer flask_name without people having a hard time.
    """
 
     def __init__ ( self , module_choices , wrapper_module ) :
         self . module_choices = module_choices
         self . wrapper_module = wrapper_module
         self . prefix = wrapper_module + '.'
         self . prefix_cutoff = wrapper_module . count ( '.' ) + 1
 
     def __eq__ ( self , other ) :
         return self . __class__ . __module__ == other . __class__ . __module__ and \
               self . __class__ . __name__ == other . __class__ . __name__ and \
               self . wrapper_module == other . wrapper_module and \
               self . module_choices == other . module_choices
 
     def __ne__ ( self , other ) :
         return not self . __eq__ ( other )
 
     def install ( self ) :
         sys . meta_path [ : ] = [ x for x in sys . meta_path if self != x ] + [ self ]
 
     def find_module ( self , fullname , path = None ) :
         if fullname . startswith ( self . prefix ) :
             return self
 
     def load_module ( self , fullname ) :
         if fullname in sys . modules :
             return sys . modules [ fullname ]
         modname = fullname . split ( '.' , self . prefix_cutoff ) [ self . prefix_cutoff ]
         for path in self . module_choices :
             realname = path % modname
             try :
                 __import__ ( realname )
             except ImportError :
                 exc_type , exc_value , tb = sys . exc_info ( )
                 # since we only establish the entry in sys.modules at the
                 # very this seems to be redundant, but if recursive imports
                 # happen we will call into the move import a second time.
                 # On the second invocation we still don't have an entry for
                 # fullname in sys.modules, but we will end up with the same
                 # fake module name and that import will succeed since this
                 # one already has a temporary entry in the modules dict.
                 # Since this one "succeeded" temporarily that second
                 # invocation now will have created a fullname entry in
                 # sys.modules which we have to kill.
                 sys . modules . pop ( fullname , None )
 
                 # If it's an important traceback we reraise it, otherwise
                 # we swallow it and try the next choice.  The skipped frame
                 # is the one from __import__ above which we don't care about
                 if self . is_important_traceback ( realname , tb ) :
                     reraise ( exc_type , exc_value , tb . tb_next )
                 continue
             module = sys . modules [ fullname ] = sys . modules [ realname ]
             if '.' not in modname :
                 setattr ( sys . modules [ self . wrapper_module ] , modname , module )
             return module
         raise ImportError ( 'No module named %s' % fullname )

然后在Flask的ext目录下的__init__.py文件中,初始化了该Importer。

Python
1
2
3
4
def setup ( ) :
     from . . exthook import ExtensionImporter
     importer = ExtensionImporter ( [ 'flask_%s' , 'flaskext.%s' ] , __name__ )
     importer . install ( )

总结

希望坚持阅读到本处的你,能明白Python import的用法、实现和改造方法。准备仓促,难免会有错误,欢迎大家指正和PR。

本文使用CC-BY-SA协议。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 中 import 的机制与实现 的相关文章

  • so库的反编译,反汇编

    Linux APP SO的反汇编工具 ida Pro 可以反汇编app和SO库 有函数名 但是不能反编译到code这一级别 下载最强的反编译工具 ida Pro 6 4 Plus rar 还有这个反汇编工具 没用过 转自 http bbs
  • PHP学习笔记——加密解密

    一 MD5算法 MD5消息摘要算法 Message Digest Algorithm 是R Rivest设计的 它对输入的任意长度的消息进行运算 产生一个128位的消息摘要 随着穷举攻击和密码分析的发展 MD5算法已经不再那么流行了 1 算
  • Python 中 import 的机制与实现

    转自 http python jobbole com 82604 本文所涉及到的代码在github上 概述 Python 是一门优美简单 功能强大的动态语言 在刚刚接触这门语言时 我们会被其优美的格式 简洁的语法和无穷无尽的类库所震撼 在真
  • MD5 加密算法详细介绍

    MD5是什么 message digest algorithm 5 信息 摘要算法 经常说的 MD5加密 就是它 信息 摘要算法 在下载一下东西时 经常在一些压缩包属性里 看到md5值 而且这个下载页面 很可能会在某一个地方 写了一句 此文
  • 需要注意字节序的大端(big endian)和小端(little endian)的几种情景

    大端 big endian 在内存中 按照从最低有效字节到最高有效字节的顺序存储对象 即数据的高字节 保存在内存的低地址中 而数据的低字节 保存在内存的高地址中 小端 little endian 在内存中 按照从最高有效字节到最低有效字节的
  • linux shell 编程

    转自 http blog csdn net fpmystar article details 4183678 和 http blog csdn net buutterfly article details 6615162 在进行linux测
  • C++智能指针简单剖析

    转自 https www cnblogs com lanxuezaipiao p 4132096 html 导读 最近在补看 C Primer Plus 第六版 这的确是本好书 其中关于智能指针的章节解析的非常清晰 一解我以前的多处困惑 C
  • C++ STL之vector用法总结

    转自 https www cnblogs com zhonghuasong p 5975979 html 介绍 vector是表示可变大小数组的序列容器 就像数组一样 vector也采用的连续存储空间来存储元素 也就是意味着可以采用下标对v
  • 机器学习——深度学习(Deep Learning)

    Deep Learning是机器学习中一个非常接近AI的领域 其动机在于建立 模拟人脑进行分析学习的神经网络 最近研究了机器学习中一些深度学习的相关知识 本文给出一些很有用的资料和心得 Key Words 有监督学习与无监督学习 分类 回归
  • 广度/宽度优先搜索(BFS)

    转自 https blog csdn net raphealguo article details 7523411 1 前言 广度优先搜索 也称宽度优先搜索 缩写BFS 以下采用广度来描述 是连通图的一种遍历策略 因为它的思想是从一个顶点V
  • 理解CPU/寄存器/内存之间的关系

    转自 https blog csdn net qq 27689785 article details 82975575 CPU 寄存器 内存 因为要了解多线程 自然少不了一些硬件知识的科普 我没有系统学习过硬件知识 仅仅是从书上以及网络上看
  • R与SPSS、SAS相比较_Python 在数据分析工作中的地位与R语言、SAS、SPSS 比较如何?

    转自 http m elecfans com article 611407 html 统计分析的软件和程序分析 能够用来做统计分析的软件和程序很多 目前应用比较广泛的包括 SPSS SAS R语言 Matlab S PLUS S Miner
  • C/C++编程笔记:C++中的指针与引用,又在什么时候使用?

    C和C 支持与大多数其他编程语言不同的指针 其他语言包括C Java Python Ruby Perl和PHP 从表面上看 引用和指针非常相似 都用于使一个变量提供对另一变量的访问 两者都提供了许多相同的功能 因此通常不清楚这些不同机制之间
  • QT学习——QFileSystemModel与QTreeView显示文件夹下的文件信息

    最近因为项目需求 使用QT做界面 新手学习 记录一些笔记 虽然QT已经做好了标准对话框的国际化 但是有时候对于中文的翻译可能达不到我们期望的 所以就需要我们自己来修改 比如下面的代码中 利用了国际化 写在main函数中 QApplicati
  • unique_ptr的使用和陷阱

    转自 https blog csdn net qq 33266987 article details 78784286 unique ptr的使用 分配内存 与shared ptr不同 unique ptr没有定义类似make shared
  • make时遇到File `Makefile' has modification time 4e+04 s in the future的解决办法

    1 原因 是虚拟机时间和电脑时间不匹配造成 2 解决办法 在VMware 菜单虚拟机 M gt 设置 S gt 选项下设置开启时间同步 然后重启虚拟机 3 若还出现 warning Clock skew detected Your buil
  • 24时区来源,CST,CET,UTC,DST,Unix时间戳概述、关系、转换

    全球24个时区的划分 相较于两地时间表 显示世界各时区时间和地名的世界时区表 Universal WorldTime 就显得精密与复杂多 通常世界时区表的表盘上会标示着全球24个时区的城市名称 全球24个时区是如何产生的 过去世界各地原本各
  • QT学习——QTreeView获取选中单行数据和多行数据

    个人感觉QTreeView有些地方的使用没有MFC的CListCtrl方便 比如在不响应单击信号的情况下 获取选中行的数据 单行和多行 也许因为我是新手吧 一 获取单行选中的数据 QModelIndex selected ui treeVi
  • Thread Local Storage---__thread 关键字的使用方法

    转自 http blog csdn net yusiguyuan article details 22938671 thread是GCC内置的线程局部存储设施 存取效率可以和全局变量相比 thread变量每一个线程有一份独立实体 各个线程的
  • 开源软件许可证—GPL、AGPL、LGPL、Apache、ZLIB/LIBPNG、MIT

    转自 http www dushibaiyu com 2013 08 E5 BC 80 E6 BA 90 E8 BD AF E4 BB B6 E8 AE B8 E5 8F AF E8 AF 81 gpl E3 80 81agpl E3 80

随机推荐

  • shell实例流程控制&函数

    条件 if then elif then fi if的条件部分经常使用test EXPRESSION或 EXPRESSION 实现 test的用法可以参见test if 条件1 if 条件1 then then 执行语句1 elif 条件2
  • MetaMask安装使用指南

    前言 MetaMask是一个以太坊钱包插件 虽然只能在Chrome浏览器中使用 但作为以太坊钱包的metamask却很受以太坊开发者欢迎 MetaMask除了是一个简单的钱包 它主要卖点是让使用者可以很容易跟以太坊的智能合约互动 或者说说M
  • DLUT C++上机作业(实验六)

    注意 博客所有代码在VS上均能编译通过 codeblocks等编译器可能因为某些变量名无法识别而无法编译 我的VS上不能用end做变量名就很迷呀 2 有一个交通工具类vehicle 将它作为基类派生小车类car 卡车类truck和轮船类bo
  • Java面试必备,JVM核心知识点总结!

    JVM基础 程序计数器 Program Counter Register CPU中的寄存器 作用 记住下一条JVM指令 特点 线程私有 唯一一个不会出现内存溢出的区域 虚拟机栈 Java virtual mechine Stack 线程私有
  • 数据库查询: 列出表的所有字段,“*”符号,查询指定字段数据,DISTINCT查询,IN查询,BETWEEN AND查询,LIKE模糊查询,对查询结果排序,分组查询,统计分组查询

    数据库查询 列出表的所有字段 符号 查询指定字段数据 DISTINCT查询 IN查询 BETWEEN AND查询 LIKE模糊查询 对查询结果排序 分组查询 统计分组查询 列出表的所有字段 通过SQL语句SELECT列出表的所有字段 具体语
  • 软考-嵌入式系统设计师-笔记:嵌入式系统软件基础知识

    文章目录 嵌入式软件基础知识 嵌入式操作系统基础知识 任务调度 信号量 页面置换算法 嵌入式系统程序设计 嵌入式软件基础知识 嵌入式软件分类 系统软件 控制和管理嵌入式系统资源 为嵌入式应用提供支持的各种软件 如设备驱动程序 嵌入式操作系统
  • build中配置resource配置,来防止资源导出失败

  • 我最喜爱的十大技术文档写作工具

    转载 老实说 我爱死微软的Word了 Adobe FrameMaker也曾辉煌过 不过你懂的 这东西有时候会令人抓狂 过去5年来 我一直使用同一套写作工具 我也曾尝试过一些新的工具 可我最终还是很专情于我的老相好们 在这里我总结了一下我所用
  • MATLAB中GUI界面内数据的读取和存储操作

    要求GUI界面的输入数据为int16中频数据文件 输出数据也为int16中频数据文件 第一步 获取数据函数 uigetfile 先自己存储数据用于验证 将仿真数据以int16的格式存于txt文件中 分I O两路 I路代表实部 O路代表虚部
  • STM32f10x学习----ADC和DMA功能 后附具体操作及使用过程中遇到的问题

    学习某一个东西 我们首先要了解这个东西的定义是什么 用来干什么的 怎么用 用的过程中有什么注意事项 这些都OK了 那么我们就算是基本掌握他了 0 前言 ADC Analog to Digital Converter的缩写 指模 数转换器或者
  • PyTorch错误定位系列之CUDA error: device-side assert triggered

    PyTorch错误定位系列之CUDA error device side assert triggered Introduction 本栏目只是提供一些自己遇到的错误的解决思路 Background 我昨天写了个模型加了focal loss
  • 遗传算法GA优化BP神经网络(GA-BP)回归预测-Matlab代码实现

    一 前言 代码获取 评论区或者私信获取 遗传算法 Genetic Algorithm GA 和反向传播神经网络 Backpropagation Neural Network BPNN 都是常用的优化算法和模型 可以联合使用进行回归预测问题的
  • python多线程低效问题

    重点 Python由于有全锁局的存在 同一时间只能有一个线程执行 并不能利用多核优势 终于找到cpu利用率低的原因了 Python解释执行原理 我是一个Python线程 我的工作就是解释执行程序员编写的Python代码 之所以说是解释执行
  • 2018年Android最新面试题(一)

    最近在忙着找工作 所以趁热打铁写一份Android最新的面试题 希望可以帮助到大家 一直被问的问题Glide的源码 重点 最好和Picasso比较着说 Glide原理 自己看 https www jianshu com p 3d699bf0
  • APP过度索取问题严重,该如何有效解决?

    近几年移动应用市场发展快速 APP种类功能繁多 给人们的生活和工作带来了无限便捷 然而事物的发展必然有对立面 APP获取用户数据问题突出 同时加大了信息泄露的风险 工信部及各通信管理局等相关部门针对APP问题频频通报 使得移动应用开发商处于
  • 封闭岛屿数量 -- 二维矩阵的dfs算法

    1254 统计封闭岛屿的数目 这道题和 岛屿数量 二维矩阵的dfs算法 类似 区别在于不算边缘部分的岛屿 那其实很简单 把上 题中那些靠边的岛屿排除掉 剩下的就是 封闭岛屿 了 关于岛屿的相似题目 岛屿数量 二维矩阵的dfs算法 封闭岛屿数
  • openEuler 22.03-LTS 基础配置

    文章目录 1 设置语言环境 1 1 显示当前语言环境状态 1 2 列出可用的语言环境 1 3 设置语言环境 2 设置键盘 2 1 显示当前设置 2 2 列出可用的键盘布局 2 3 设置键盘布局 3 设置日期和时间 3 1 使用timedat
  • 聚合工程是什么?与微服务有什么区别和联系?

    1 聚合的概念 把项目的各个模块 子工程 聚合在一起构建 一般用于分模块开发 最后整体打包发布 Maven Project独立运行 Maven Module无法独立运行 2 聚合工程开发步骤 1 根项目是一个pom项目 2 子模块 Mave
  • Eigen 简单矩阵运算

    用到 Eigen Core 和 Eigen Dense 模块 矩阵定义 Eigen Matrix lt 数据类型 行数 列数 gt 矩阵名称 已经提供的矩阵类型 Vector3d 向量名称 实质上是 Eigen Matrix
  • Python 中 import 的机制与实现

    转自 http python jobbole com 82604 本文所涉及到的代码在github上 概述 Python 是一门优美简单 功能强大的动态语言 在刚刚接触这门语言时 我们会被其优美的格式 简洁的语法和无穷无尽的类库所震撼 在真