Python模块包导入
共 4902字,需浏览 10分钟
·
2021-12-27 20:22
从下面的目录结构可以看出,mypackage
是一个包,其中包含了a
和b
两个子模块且其中包含各一个文件,而且每个模块都包含了__init__.py
这个文件。
$ tree
mypackage
|____ __init__.py
|____ a
| |____ __init__.py
| |____ bar.py
|____ b
|____ __init__.py
|____ foo.py
$ cat mypackage/a/bar.py
BAR = 1
$ cat mypackage/b/foo.py
FOO = 2
如下所示,就是最基础的导入方法,也叫做隐式的相对导入。隐式的相对导入就是没有告诉解释器我这个import
相对于谁导入的,但是它默认的是相对的是当前的模块。
In [1]: import mypackage
In [2]: import mypackage.a
In [3]: import mypackage.a.bar
In [4]: from mypackage import a
In [5]: from mypackage.b import foo
In [6]: from mypackage.a.bar import BAR
显式导入和隐式导入
在Python
包导入的这个概念里面,并没有一个绝对的导入,因为它没有Linux
系统的根文件路径这个概念。在Python
中,都是通过相对导入的,只不过分为显式导入和隐式导入。
现在,我们修改bar.py
中的内容如下所示,之后我们引用bar.py
的时候就能够使用foo.py
中的FOO
变量了。其中,.
代表了当前模块,而..
代表了上层的模块,...
就代表了再上层模块,以此类推。
$ cat mypackage/a/bar.py
BAR = 1
from ..b.foo import FOO
In [1]: from mypackage/a/bar.py import FOO
In [2]: FOO
Out[2]: 2
当然,我们还可以使用如下两种的相对导入的写法。第一种使用了.
就是相对于当前模块导入的foo
这个模块,而第二种使用了.foo
就是相对于当前模块的foo
模块导入了FOO
这个变量。其中,导入的方式不同,调用的方式也就不同了。
我们可以看到,在__init__.py
中导入包下的某些模块或者模块里面的内容,这种方式是比较流行的。它相当于给这个包,提供了一个入口,我不再需要在这个模块里面的子模块去找了。
$ cat mypackage/b/__init__.py
from . import foo
from .foo import FOO
In [1]: from mypackage/b import foo, FOO
In [2]: foo.FOO, FOO
Out[2]: (2, 2)
相对导入适合场景
第一个就是,在大型项目中,代码目录非常复杂,模块层级很深。使用相对导入可维护性更强,导入语句更简洁。第二个就是,在项目早期,由于需求变动很大,会偶尔改变某一个顶层包的名字或者移动位置。
一般情况下,如果模块的层级超过三级就需要使用这种显式的相对导入方法,否则就会使用默认的那种隐式的相对导入。
可以看下Flask
里面的入口文件__init__.py
里面的内容,分为三个部分。第一部分是非Flask
包里面的内容,第二部分是Flask
包内模块中的类和函数,第三部分直接导入json
这个子包,而且因为非常常用而暴露了jsonify
这个json
函数。
$ cat flask/__init__.py
from werkzeug.exceptions import abort
from werkzeug.utils import redirect
from jinja2 import Markup, escape
from .app import Flask, Request, Response
from .config import Configfrom .blueprints import Blueprint
from .templating import render_template, render_template_string
...
from . import json
jsonify = json.jsonify
常见模块导入错误
当我们在Python2
中使用IPython
或者Python
解释器的时候,相对导入只能放在代码里面,而不能直接运行的。而在Python3
中相对导入会有一些区别,相对于当前目录是可以导入,其他的都会报错。那什么方法是对的呢?就是之前的哪个Flask
入口文件的示例,将导入写在代码里面就可以了。
# Python2
In [1]: from . import mypackage
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
input -1-b2a156b38b60> in <module>()----> 1 from . import mypackage
ValueError: Attempted relative import in non-package
In [2]: from .mypackage import b
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
input -2-0086e0c5eb87> in <module>()----> 1 from .mypackage import b
ValueError: Attempted relative import in non-package
In [1]: from . import mypackage
In [2]: mypackage
Out[2]:
'mypackage' from '/Users/escape/mypackage/__init__.py'>
In [3]: from .mypackage import b
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
in () ----> 1 from .mypackage import b
ModuleNotFoundError: No module named '__main__.mypackage'; '__main__' is not a package
In [4]: from .mypackage.b import FOO
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
in () ----> 1 from .mypackage.b import FOO
ModuleNotFoundError: No module named '__main__.mypackage'; '__main__' is not a package
禁用隐式的相对导入
这个小节的语句from __future__ import absolute_import
,可能之前我们都见过,它会禁用隐式的相对导入(implicit relative import
)而不会禁用显式的相对导入(explicit relative import
)。
# 隐式相对导入
In [1]: import mypackage
# 显式相对导入
In [2]: from . import mypackage
像上面的示例中,是可以导入是可以运行的,以为mypackage
这个名称一般都不会有冲突的。但是,如果和标准库有冲突的话,那就有问题了。如果我们想引用标准库中的os
模块,却引入了mypackage
模块下的os
文件中的内容,那不就尴尬了。
$ cat mypackage/os.py
a = 1
$ cat mypackage/__init__.py
from os import a
# Python2
In [1]: from mypackage import a
In [2]: a
Out: 1
而当我们禁用隐式的相对引入,再次执行的时候,会报错提示说os
模块中并没有a
这个模块。因为它引用的是标准库中的os
模块,并不是我们定义的。
而在Python3
中,是禁用隐式的相对导入这种方式的。不是不允许我们这样使用,而是导入os
模块的话会优先引入标准库模块。
$ cat mypackage/__init__.py
from __future__ import absolute_import
from os import a
In [1]: from mypackage import a
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
-1 -aba2f1bd100d> in <module>()----> 1 from mypackage import a/Users/escape/mypackage/__init__.py in <module>()
1 from __future__ import absolute_import
----> 2 from os import a
ImportError: cannot import name a
优先属性和模块的导入
我们知道,使用from xxx import *
这样一次性导入xxx
模块下全部内容的方式的写法是一个不好的形式。所以,可以在包的__init__.py
文件里面,加入一个__all__
这样一个属性。不在__all__
之后的列表中,是不能通过import *
的这种方式导入的。
这样,通常是为了减少模块导入的时间,也减少了不必导入的内容。但是这样使用方式,只适用于import *
的这种方式。所以,使用from mymodule import b
也是可以导入b
的。
$ cat mymodule.py
__all__ = ['a']
a = 1
b = 2
In [1]: from mymodule import *
In [2]: a
Out[2]: 1
In [3]: b
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
3 -89e6c98d9288> in <module>()----> 1b
NameError: name 'b' is not defined
文章转载:Python编程学习圈
(版权归原作者所有,侵删)
点击下方“阅读原文”查看更多