Python模块包导入

马哥Linux运维

共 4902字,需浏览 10分钟

 ·

2021-12-27 20:22



从下面的目录结构可以看出,mypackage是一个包,其中包含了ab两个子模块且其中包含各一个文件,而且每个模块都包含了__init__.py这个文件。

$ treemypackage|____ __init__.py|____ a|     |____ __init__.py|     |____ bar.py|____ b      |____ __init__.py      |____ foo.py

$ cat mypackage/a/bar.pyBAR = 1
$ cat mypackage/b/foo.pyFOO = 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.pyBAR = 1from ..b.foo import FOO
In [1]: from mypackage/a/bar.py import FOO
In [2]: FOOOut[2]: 2

当然,我们还可以使用如下两种的相对导入的写法。第一种使用了.就是相对于当前模块导入的foo这个模块,而第二种使用了.foo就是相对于当前模块的foo模块导入了FOO这个变量。其中,导入的方式不同,调用的方式也就不同了。

我们可以看到,在__init__.py中导入包下的某些模块或者模块里面的内容,这种方式是比较流行的。它相当于给这个包,提供了一个入口,我不再需要在这个模块里面的子模块去找了。

$ cat mypackage/b/__init__.pyfrom . import foofrom .foo import FOO
In [1]: from mypackage/b import foo, FOO
In [2]: foo.FOO, FOOOut[2]: (2, 2)
  • 相对导入适合场景

第一个就是,在大型项目中,代码目录非常复杂,模块层级很深。使用相对导入可维护性更强,导入语句更简洁。第二个就是,在项目早期,由于需求变动很大,会偶尔改变某一个顶层包的名字或者移动位置。

一般情况下,如果模块的层级超过三级就需要使用这种显式的相对导入方法,否则就会使用默认的那种隐式的相对导入。

可以看下Flask里面的入口文件__init__.py里面的内容,分为三个部分。第一部分是非Flask包里面的内容,第二部分是Flask包内模块中的类和函数,第三部分直接导入json这个子包,而且因为非常常用而暴露了jsonify这个json函数。

$ cat flask/__init__.pyfrom werkzeug.exceptions import abortfrom werkzeug.utils import redirectfrom jinja2 import Markup, escape
from .app import Flask, Request, Responsefrom .config import Configfrom .blueprints import Blueprintfrom .templating import render_template, render_template_string...
from . import jsonjsonify = json.jsonify
  • 常见模块导入错误

当我们在Python2中使用IPython或者Python解释器的时候,相对导入只能放在代码里面,而不能直接运行的。而在Python3中相对导入会有一些区别,相对于当前目录是可以导入,其他的都会报错。那什么方法是对的呢?就是之前的哪个Flask入口文件的示例,将导入写在代码里面就可以了。

# Python2In [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
# Python3In [1]: from . import mypackage
In [2]: mypackageOut[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.pya = 1
$ cat mypackage/__init__.pyfrom os import a

# Python2In [1]: from mypackage import a
In [2]: aOut: 1

而当我们禁用隐式的相对引入,再次执行的时候,会报错提示说os模块中并没有a这个模块。因为它引用的是标准库中的os模块,并不是我们定义的。

而在Python3中,是禁用隐式的相对导入这种方式的。不是不允许我们这样使用,而是导入os模块的话会优先引入标准库模块。

$ cat mypackage/__init__.pyfrom __future__ import absolute_importfrom 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 = 1b = 2
In [1]: from mymodule import *
In [2]: aOut[2]: 1
In [3]: b---------------------------------------------------------------------------NameError Traceback (most recent call last)3-89e6c98d9288> in <module>()----> 1b
NameError: name 'b' is not defined

文章转载:Python编程学习圈 
(版权归原作者所有,侵删)

点击下方“阅读原文”查看更多

浏览 85
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报