Python导入语句的权威指南!
点击上方关注,All in AI中国
我几乎从来没有能够在第一时间编写正确的Python导入语句。
Python 2.7和Python 3.6(我在这里测试的两个版本)之间的行为不一致,并且没有一种方法可以保证导入始终有效。这篇文章是我如何解决常见的导入问题。除非另有说明,否则此处的所有示例均适用于Python 2.7和3.6。
目录
摘要/要点
基本定义
示例目录结构
什么是 import?
Python import和sys.path的基础知识
有关sys.path的更多信息
所有的__init__.py
将脚本文件夹转换为可导入的模块包
运行包初始化代码
使用导入的模块或包中的对象
使用dir()检查导入模块的内容
导入包
绝对与相对导入
案例
案例1:提前知道sys.path
案例2:sys.path可能会改变
案例3:sys.path可以改变(取2)
案例4:从父目录导入
Python 2与Python 3
摘要/要点
- import语句搜索sys.path中的路径列表
- sys.path始终包含在命令行上调用的脚本的路径,并且与命令行上的工作目录无关。
- 导入包在概念上与导入该包的__init__.py文件相同
基本定义
- 模块:模块的名字就是不带 .py 后缀的文件名
- 内置模块:一个编译到Python解释器中的"模块"(用C编写),因此没有* .py文件。
- 包:包含名为__init__.py的文件的任何文件夹,它的名称是文件夹的名称。在Python 3.3及更高版本中,任何文件夹(即使没有__init__.py文件)都被视为包
- 对象:在Python中,几乎所有东西都是对象 - 函数,类,变量等。
示例目录结构
请注意,我们不会在根测试/文件夹中放置__init__.py文件。
什么是import?
导入模块时,Python会运行模块文件中的所有代码。导入包时,如果存在此类文件,Python将运行包的__init__.py文件中的所有代码。模块中定义的所有对象或包的__init__.py文件都可供导入程序使用。
Python import和sys.path的基础知识
根据Python文档,以下是import语句如何搜索要导入的正确模块或包:
导入名为spam的模块时,解释器首先搜索具有该名称的内置模块。如果未找到,则会在变量sys.path给出的目录列表中搜索名为spam.py的文件。 sys.path从这些位置初始化:
- 包含输入脚本的目录(或未指定文件时的当前目录)。
- PYTHONPATH(目录名列表,语法与shell变量PATH相同)。
- 依赖于安装的默认值。
初始化后,Python程序可以修改sys.path。包含正在运行的脚本的目录位于搜索路径的开头,位于标准库路径之前。这意味着将加载该目录中的脚本,而不是库目录中的同名模块。
资料来源:Python 2和3
从技术上讲,Python的文档是不完整的。解释器不仅会查找名为spam.py的文件(即模块),还会查找名为spam的文件夹(即包)。
请注意,Python解释器首先搜索内置模块列表,这些模块是直接编译到Python解释器中的模块。此内置模块列表与安装有关,可以在sys.builtin_module_names(Python 2和3)中找到。一些通常包含的内置模块是sys(总是包括)、math、itertools和time等。
与搜索路径中的第一个内置模块不同,Python标准库(非内置函数)中的其余模块位于当前脚本的目录之后。这会导致令人困惑的行为:可以"替换"Python标准库中的一些但不是所有模块。例如,在我的计算机上(Windows 10,Python 3.6),数学模块是内置模块,而随机模块则不是。因此,在start.py中导入数学将从标准库导入数学模块,而不是我自己的math.py文件在同一目录中。但是,在start.py中导入随机将导入我的random.py文件,而不是标准库中的随机模块。
此外,Python导入区分大小写,这是不同的。
函数pkgutil.iter_modules(Python 2和3)可用于获取给定路径中所有可导入模块的列表:
来源
如何在python中获取内置模块列表?
(https://stackoverflow.com/questions/8370206/how-to-get-a-list-of-built-in-modules-in-python)
感谢etene指出内置模块与Python标准库中的其他模块之间的区别(问题2)
(https://github.com/etene)
有关sys.path的更多信息
要查看sys.path中的内容,请在解释器中或作为脚本运行以下命令:Python的sys.path文档将其描述为......
一个字符串列表,指定模块的搜索路径。从环境变量PYTHONPATH初始化,加上依赖于安装的默认值。
在程序启动时初始化时,此列表的第一项path [0]是包含用于调用Python解释器的脚本的目录。如果脚本目录不可用(例如,如果以交互方式调用解释器或者从标准输入读取脚本),则路径[0]为空字符串,它首先将Python引导到当前目录中的搜索模块。请注意,在作为PYTHONPATH的结果插入条目之前插入了脚本目录。
资料来源:Python 2和3
Python命令行界面的文档添加了有关从命令行运行脚本的以下内容。具体来说,当运行python <script> .py时,那么......
如果脚本名称直接引用Python文件,则包含该文件的目录将添加到sys.path的开头,并且该文件将作为主模块执行。
资料来源:Python 2和3
让我们回顾一下Python搜索要导入的模块的顺序:
- Python标准库中的模块(例如math,os)
- sys.path指定的目录中的模块或包:
·如果Python解释器以交互方式运行:
sys.path [0]是空字符串''。这告诉Python搜索启动解释器的当前工作目录,即Unix系统上的pwd输出。
如果我们使用python <script> .py运行脚本:
sys.path [0]是<script> .py的路径
·PYTHONPATH环境变量中的目录
·默认的sys.path位置
请注意,在运行Python脚本时,sys.path不关心当前的"工作目录"是什么。它只关心脚本的路径。例如,如果我的shell当前位于test /文件夹中并运行python ./packA/subA/subA1.py,那么sys.path包含test / packA / subA /但不是test /。
此外,sys.path在所有导入的模块之间共享。例如,假设我们调用python start.py。让start.py导入packA.a1,让a1.py打印出sys.path。然后sys.path将包含test /(start.py的路径),但不包括test / packA /(a1.py的路径)。这意味着a1.py可以调用import,因为other.py是test /中的文件。
所有的__init__.py
__init__.py文件有2个函数。
- 将脚本文件夹转换为可导入的模块包(在Python 3.3之前)
- 运行包初始化代码
将脚本文件夹转换为可导入的模块包
为了从与我们正在编写的脚本不同的目录(或者我们运行Python交互式解释器的目录)中导入模块或包,该模块需要位于包中。
如上所述,具有名为__init__.py的文件的任何目录都是Python包。此文件可以为空。例如,在运行Python 2.7时,start.py可以导入包packA但不能导入packB,因为test / packB /目录中没有__init__.py文件。
由于采用了隐式命名空间包,因此不适用于Python 3.3及更高版本。基本上,Python 3.3+将所有文件夹视为包,因此不再需要空__init__.py文件,可以省略。
例如,packB是命名空间包,因为它在文件夹中没有__init__.py文件。如果我们在test /目录中启动Python 3.6交互式解释器,那么我们得到以下输出:
来源
1.什么是init.py?(https://stackoverflow.com/questions/448271/what-is-init-py-for)
2.PEP 420:隐式命名空间包(https://www.python.org/dev/peps/pep-0420/)
运行包初始化代码
第一次导入包或其中一个模块时,如果文件存在,Python将在包的根文件夹中执行__init__.py文件。 __init__.py中定义的所有对象和函数都被视为包命名空间的一部分。
请考虑以下示例。
test/packA/a1.py
test/packA/__init__.py
test/start.py
运行python start.py的输出
*注意:如果a1.py调用import a2并运行python a1.py,则不会调用test / packA / __ init__.py,即使a2似乎是packA包的一部分。这是因为当Python运行脚本(在本例中为a1.py)时,其包含的文件夹不被视为包。
使用导入的模块或包中的对象
编写import语句有4种不同的语法。
- import <package>
- import <module>
- 来自<package> import <module或subpackage或object>
- 来自<module> import <object>
设X是导入后的任何名称。
- 如果X是模块或包的名称,那么要使用X中定义的对象,您必须编写X.object。
- 如果X是变量名,那么它可以直接使用。
- 如果X是函数名,那么可以用X()调用它
可选地,因为可以在任何导入X语句之后添加Y:将X导入为Y.这将在脚本中将X重命名为Y.请注意,名称X本身不再有效。一个常见的例子是导入numpy为np。
导入函数的参数可以是单个名称,也可以是多个名称的列表。这些名称中的每一个都可以选择通过as重命名。例如,这将是start.py中的有效import语句:import packA as pA,packA.a1,packA.subA.sa1 as sa1
示例:start.py需要在sa1.py中导入helloWorld()函数
- 解决方案1:从packA.subA.sa1导入helloWorld
我们可以通过名称直接调用函数:x = helloWorld()
- 解决方案2:从packA.subA导入sa1或等效导入packA.subA.sa1作为sa1
我们必须在函数名前加上模块的名称:x = sa1.helloWorld()
这有时比解决方案1更受欢迎,以便明确我们从sa1模块调用helloWorld函数。
- 解决方案3:导入packA.subA.sa1。
我们需要使用完整路径:x = packA.subA.sa1.helloWorld()
使用dir()检查导入模块的内容
导入模块后,使用dir()函数从模块中获取可访问名称的列表。例如,假设我导入sa1。如果sa1.py定义了一个helloWorld()函数,那么dir(sa1)将包含helloWorld。
导入包
导入包在概念上等同于将包的__init__.py文件作为模块导入。 实际上,这就是Python将包视为:
导入器只能访问导入的包的__init__.py中声明的对象。例如,由于packB缺少__init__.py文件,因此调用import packB(在Python 3.3+中)几乎没有用处,因为packB包中没有对象可用。随后对packB.b1的调用将失败,因为它尚未导入。
绝对与相对导入
绝对导入使用完整路径(从项目的根文件夹开始)到要导入的所需模块。
相对导入使用相对路径(从当前模块的路径开始)到要导入的所需所需模块。有两种类型的相对导入:
- 显式相对导入遵循<module / package> import X的格式,其中<module / package>以点为前缀。表示向上遍历的目录数量。一个点。对应当前目录;两个点..表示一个文件夹;等等
- 写入隐式相对导入,就像当前的directoy是sys.path的一部分一样。隐式相对导入仅在Python 2中受支持。它们不支持PYTHON 3。
Python文档说明了Python 3对相对导入的处理:
相对导入唯一可接受的语法来自[module]导入名称。所有导入表单都不以被解释为绝对导入。
来源:Python 3.0中的新功能
例如,假设我们正在运行start.py,它导入a1,而a1又导入了other,a2和sa1。然后a1.py中的import语句如下所示:
·绝对导入:
·显性相对导入:
·隐性相对导入:
请注意,对于相对导入,点。只能到达(但不包括)包含从命令行运行的脚本的目录。因此,from .. import在a1.py中无效。这样做会导致错误ValueError:尝试在顶级包之外进行相对导入。
一般而言,绝对进口优先于相对进口。它们避免了显性与隐式相对导入之间的混淆。此外,任何使用显式相对导入的脚本都无法直接运行:
请注意,相对导入基于当前模块的名称。由于主模块的名称始终为"main",因此用作Python应用程序主模块的模块必须始终使用绝对导入。
资料来源:Python 2和3
来源
如何在python中完成相对导入(https://stackoverflow.com/questions/4655526/how-to-accomplish-relative-import-in-python)
导入语句python3的更改(https://stackoverflow.com/questions/12172791/changes-in-import-statement-python3)
案例
案例1:提前知道sys.path
如果你只调用python start.py或python other.py,那么很容易为所有模块设置导入。在这种情况下,sys.path将始终在其搜索路径中包含test /。因此,所有导入语句都可以相对于测试/文件夹编写。
例如:测试项目中的文件需要在sa1.py中导入helloWorld()函数
解决方案:从packA.subA.sa1导入helloWorld(或上面演示的任何其他等效导入语法)
案例2:sys.path可能会改变
通常,我们希望灵活地使用Python脚本,无论是直接在命令行上运行还是作为模块导入到另一个脚本中。如下所示,这是我们遇到问题的地方,特别是在Python 3上。
示例:假设start.py需要导入需要导入sa2的a2。假设start.py总是直接运行,从不导入。我们仍希望能够自己运行a2。
看起来很简单吧?毕竟,我们只需要2个导入语句:start.py中的1个和a2.py中的另一个。
问题:这显然是sys.path更改的情况。当我们运行start.py时,sys.path包含test /。当我们运行a2.py时,sys.path包含test / packA /。
start.py中的导入语句很简单。由于start.py总是直接运行而且从不导入,我们知道test /将在运行时始终在sys.path中。然后导入a2只是导入packA.a2。
a2.py中的导入语句比较棘手。当我们直接运行start.py时,sys.path包含test /,所以a2.py应该从packA.subA import sa2调用。但是,如果我们直接运行a2.py,那么sys.path包含test / packA /。现在导入将失败,因为packA不是test / packA /中的文件夹。
相反,我们可以尝试从subA导入sa2。当我们直接运行a2.py时,这可以解决问题。但是现在我们直接运行start.py会遇到问题。在Python 3下,这会失败,因为subA不在sys.path中。 (这在Python 2中可以,因为它支持隐式相对导入。)
让我们总结一下我们关于a2.py中导入语句的发现:
为了完整起见,我还尝试使用相对导入:from .subA import sa2。这与packA.subA import sa2的结果相匹配。
解决方案(解决方法):我不知道这个问题的简洁的解决方案。以下是一些解决方法:
1.使用以test /目录为根的绝对导入(即上表中的中间列)。这可以保证直接运行start.py始终有效。要直接运行a2.py,请将其作为导入的模块而不是脚本运行:
- 将目录更改为测试/在控制台中
- python -m packA.a2
2.使用以test /目录为根的绝对导入(即上表中的中间列)。这可以保证直接运行start.py始终有效。为了直接运行a2.py,我们可以在导入sa2之前修改a2.py中的sys.path以包含test / packA /。
注意:此方法通常有效。但是,在某些Python安装下,__ file__变量可能不正确。在这种情况下,我们需要使用Python内置的检查包。有关说明,请参阅此StackOverflow答案。
3.仅使用Python 2,并使用隐式相对导入(即上表中的右列)
4.使用以test /目录为根的绝对导入,并将test /添加到PYTHONPATH环境变量中。
- 这个解决方案不便携,所以我建议不要这样做。
- 说明:永久添加目录到PYTHONPATH
案例3:sys.path可以改变(取2)
下面的例子是一个难以处理的问题。假设a2.py永远不需要直接运行,但它由start.py和a1.py直接运行。
在这种情况下,使用上述解决方案1将不起作用。但是,其他解决方案仍然有效。
案例4:从父目录导入
如果我们不修改PYTHONPATH并避免以编程方式修改sys.path,那么以下是Python导入的主要限制:
直接运行脚本时,无法从其父目录导入任何内容。
例如,如果我要运行python sa1.py,那么forsa1.py不可能从a1.py导入任何内容而无需求助于PYTHONPATH或sys.path解决方法。
起初,似乎相对导入(例如来自.. import a1)可以解决这个限制。但是,正在运行的脚本(在本例中为sa1.py)被视为"顶级模块"。尝试从此脚本上方的文件夹导入任何内容会导致此错误:ValueError:尝试相对导入超出顶级包。
我的方法是避免编写必须从父目录导入的脚本。如果必须发生这种情况,首选的解决方法是修改sys.path。
Python 2与Python 3
上面已经记录了Python 2和Python 3如何处理导入语句之间最重要的区别。这里再次重申它们,以及其他一些不那么重要的差异。
Python 2支持隐式相对导入。 Python 3没有。
Python 2需要文件夹中的__init__.py文件,以便将该文件夹视为包并使其可导入。在Python 3.3及更高版本中,由于它支持隐式命名空间包,所有文件夹都是包,无论是否存在__init__.py文件。
在Python 2中,可以从函数中的<module> import *中编写。在Python 3中,from <module> import *语法只允许在模块级别,不再在函数内部。
资料来源:
导入语句python3的更改(https://stackoverflow.com/questions/12172791/changes-in-import-statement-python3)
Python 2模块文档(https://docs.python.org/2/tutorial/modules.html#intra-package-references)
Python 3模块文档(https://docs.python.org/3/tutorial/modules.html#intra-package-references)
Python 3.0中的新功能(https://docs.python.org/3.0/whatsnew/3.0.html)
侵删