python import 中的相对引用还是绝对引用?
- 2017-11-29 16:00:00
- june
- 来源:
- http://python.jobbole.com/82604/
- 转贴 1209
一些实践经验
相对引用还是绝对引用?
上面介绍了Python的两种引用方式,都可以解决引入歧义的问题。那我们应该使用哪一种呢?
先说明一下Python的默认引用方式,在Python2.4及之前,Python只有相对引用这一种方式,在Python2.5中实现了绝对引用,但默认没有打开,需要用户自己指定使用该引用方式。在之后的版本和Python3版本,绝对引用已经成为默认的引用方式。
其次,二种引用方式各有利弊。绝对引用代码更加清晰明了,可以清楚的看到引入的包名和层次,但是,当包名修改的时候,我们需要手动修改所有的引用代码。相对引用则比较精简,不会被包名修改所影响,但是可读性较差,不如完全引用清晰。
最后,对于两种引用的方式选择,还是有争论的。在PEP8中,Python官方推荐的是绝对引用,详细理由可以参考 这儿。
123 import mypkg . siblingfrom mypkg import siblingfrom mypkg . sibling import exampleHowever, 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:
12 from . import siblingfrom . sibling import exampleStandard 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语句主要是做了二件事:
- 查找相应的module
- 加载module到local namespace
下面我们详细了解python是如何查找模块的。
查找module的过程
在import的第一个阶段,主要是完成了查找要引入模块的功能,这个查找的过程如下:
- 检查 sys.modules (保存了之前import的类库的缓存),如果module被找到,则⾛到第二步。
- 检查 sys.meta_path。meta_path 是一个 list,⾥面保存着一些 finder 对象,如果找到该module的话,就会返回一个finder对象。
- 检查⼀些隐式的finder对象,不同的python实现有不同的隐式finder,但是都会有 sys.path_hooks, sys.path_importer_cache 以及sys.path。
- 抛出 ImportError。
sys.modules
对于第一步中sys.modules,我们可以打开Python来实际的查看一下其内容:
12345678 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 darwinType "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对象。
1
2
|
In
[
6
]
:
sys
.
meta_path
Out
[
6
]
:
[
]
|
我们可以向sys.meta_path中添加一些定义的finder,来实现对Python加载模块的修改。比如下例,我们实现了一个会将每次加载包的信息打印出来的finder。
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包时所发生的事情。
12345678 liuchang @ localhost ~ / Codes / pycon / ex5_meta _path$ python finder1 . pyImporting socket None NoneImporting _socket None NoneImporting functools None NoneImporting _functools None NoneImporting _ssl None NoneImporting 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库。
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的全部代码如下:
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。主要有如下几种:
- __import__ : Python的内置函数
- imputil : Python的import工具库,在py2.6被声明废弃,py3中彻底移除。
- imp : Python2 的一个import库,py3中移除
- importlib : Python3 中最新添加,backport到py2.7,但只有很小的子集(只有一个函数)。
Python2 所有关于import的库的列表参见 Importing Modules。Python3 的可以参考 Importing Modules PEP 0302 — New Import Hooks 提案详细的描述了importlib的目的、用法。