如何导入一个在上级目录中的Python类?

304

我想从一个位于当前目录上面的目录中的文件中继承一个类。

能否使用相对导入来导入该文件?

11个回答

240

from ..subpkg2 import mod

根据Python文档:当在一个包层级结构中时,使用两个点,如import语句文档所述:

在指定要导入的模块时,您不必指定模块的绝对名称。当一个模块或包包含在另一个包中时,可以在同一顶级包内进行相对导入,而无需提及包名称。通过在from之后的指定模块或包中使用前导点,可以指定要向上遍历当前包层次结构的层数,而无需指定确切的名称。一个前导点表示存在该导入的模块的当前包。 两个点表示向上一级包。 三个点表示向上两个级别,以此类推。因此,如果您从 pkg 包中的模块执行 from. import mod ,则最终将导入 pkg.mod 。如果您从 pkg.subpkg1 内部执行 from..subpkg2 import mod ,则将导入 pkg.subpkg2.mod 。相对导入的规范包含在PEP 328中。

PEP 328处理绝对/相对导入。


7
up1 = os.path.abspath('..')sys.path.insert(0, up1) - rp.
Pep 328 只显示 Python 版本:2.4、2.5、2.6。版本 3 留给更有经验的开发者。 - gimel
80
这会触发错误 ValueError: 尝试相对导入超出顶层包。 - Carlo
49
导致 ImportError 错误:尝试进行相对导入,但没有已知的父包。 - Rotkiv
1
@Rotkiv 请参阅 https://dev59.com/5mYq5IYBdhLWcg3w30Tc#14132912。你可能需要使用 python -m 命令从父目录运行脚本并使用绝对导入。有许多技巧可以帮助你进行调试,尝试打印 name__、__package 和 sys.path。 - Almenon

194
import sys
sys.path.append("..") # Adds higher directory to python modules path.

3
对我有用。添加此内容后,我可以直接导入父模块,无需使用“..”。 - Evan Hu
13
大致来说,只有当应用程序的PWD值——即其当前目录是父目录的子目录时,它才能正常工作。因此,即使它能工作,许多种系统性的变化都可能将其移位。 - Phlip
1
这对我来说也适用于导入更高层次的模块。我与os.chdir("..")一起使用它来加载其他更高层次的文件。 - Surendra Shrestha
这似乎触发了与gimel上面的答案相同的错误。 - Carlo
请不要这样做。如果您需要导入一个更高级的目录或通过..访问任何其他目录,那么您所做的是创建一个包,因此您应该构建该包,并使用虚拟环境在本地系统上进行安装管理。更多信息请参见此答案:https://stackoverflow.com/questions/77507389/python-project-directory-structure-modulenotfounderror - undefined

102

@gimel 的回答是正确的,如果你可以保证他提到的包层次结构。如果你不能保证这一点——如果你的真正需求与目录紧密相关而没有任何必要与打包有关——那么你需要使用 __file__ 来找到父目录(几个 os.path.dirname 调用就行了;-),然后(如果该目录尚未在 sys.path 中)临时将该目录插入到 sys.path 的最前面,__import__,再将该目录删除——确实是一项混乱的工作,但“当你必须时,你必须”(而 Python 力求永远不阻止程序员做出必须做的事情——就像 ISO C 标准在其前言的“C 精神”部分所说的那样!)。

以下是一个可能适合您的示例:

import sys
import os.path
sys.path.append(
    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))

import module_in_parent_dir

3
这可能会将位于Python包内部的目录添加到sys.path中,从而使同一模块在不同名称下可用,造成相应的错误。pypy中的autopath.py或twisted中的_preamble.py通过使用搜索条件来识别上级包,在向上遍历目录时解决了这个问题。 - jfs
7
你可能想在所需的导入之后执行类似于 sys.path.remove(pathYouJustAdded) 的操作,以便不保留这个新路径。 - Matthias

31

从当前目录的上一级目录导入模块:

from .. import module

58
尝试相对于顶级包以外的位置导入。 - RicardoE
52
使用 from .. import module 时,按照建议操作后出现了 ValueError: Attempted relative import in non-package 错误。 - user3428154
2
导入错误:尝试相对导入,但没有已知的父包。出现了这个错误。 - SayAz
这是因为你需要将你的脚本作为模块运行,@user3428154 和 SayAz。 - Dr_Bunsen

12

如何加载一个上层目录的模块

前言:我对之前的回答进行了大量改写,希望能够帮助大家更好地了解Python生态系统,并且帮助大家成功使用Python的import系统。

这篇文章将涵盖包内相对导入,我认为这是最可能出现在OP问题中的情况。

Python是一个模块化系统

这就是为什么我们写import foo来从根命名空间加载一个名为"foo"的模块,而不是写:

foo = dict();  # please avoid doing this
with open(os.path.join(os.path.dirname(__file__), '../foo.py') as foo_fh:  # please avoid doing this
    exec(compile(foo_fh.read(), 'foo.py', 'exec'), foo)  # please avoid doing this

Python不与文件系统耦合

这就是为什么我们可以将Python嵌入到没有提供虚拟文件系统的环境中,例如Jython。

不受文件系统的耦合让导入变得灵活,这种设计允许从存档/zip文件中导入,导入单例,字节码缓存,cffi扩展,甚至远程代码定义加载等。

那么,如果导入与文件系统没有耦合,"上一级目录"是什么意思呢?我们必须选择一些启发式方法,但我们可以做到这一点,例如在package中工作时,已经定义了一些启发式方法,使得相对导入,如.foo..foo可以在同一个包中工作。太酷了!

如果您真的想将源代码加载模式与文件系统耦合,可以这样做。您将不得不选择自己的启发式方法,并使用某种导入机制,我推荐使用importlib

Python的importlib示例大致如下:

import importlib.util
import sys

# For illustrative purposes.
file_path = os.path.join(os.path.dirname(__file__), '../foo.py')
module_name = 'foo'

foo_spec = importlib.util.spec_from_file_location(module_name, file_path)
# foo_spec is a ModuleSpec specifying a SourceFileLoader
foo_module = importlib.util.module_from_spec(foo_spec)
sys.modules[module_name] = foo_module
foo_spec.loader.exec_module(foo_module)

foo = sys.modules[module_name]
# foo is the sys.modules['foo'] singleton

打包

这里有一个很棒的官方示例项目:https://github.com/pypa/sampleproject

Python 包是关于您源代码的信息集合,可以告知其他工具如何将您的源代码复制到其他计算机,并如何将您的源代码集成到该系统的路径中,以便import foo适用于其他计算机(无论解释器、主机操作系统等)。

目录结构

我们假设有一个名为 foo 的包,在某个目录中(最好是空目录)。

some_directory/
    foo.py  # `if __name__ == "__main__":`  lives here

我更倾向于将setup.py创建为foo.py的同级文件,因为这样可以使编写setup.py文件更简单。但是,如果您愿意,可以编写配置来更改/重定向setuptools默认执行的所有操作。例如,将foo.py放在“src /”目录下是比较流行的做法,本文不涉及此内容。
some_directory/
    foo.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    py_modules=['foo'],
)

.

python3 -m pip install --editable ./  # or path/to/some_directory/

"editable",也称为-e,将再次重定向导入机制以加载此目录中的源文件,而不是将当前精确文件复制到安装环境的库中。这也可能导致开发人员计算机上的行为差异,请务必测试您的代码!除了pip之外还有其他工具,但我建议pip是最好的入门工具 :) 我还喜欢将foo制作成“包”(包含__init__.py的目录)而不是模块(单个“.py”文件),“包”和“模块”都可以加载到根名称空间中,模块允许嵌套命名空间,如果我们想要进行“相对于上一级目录”的导入,则这很有帮助。
some_directory/
    foo/
        __init__.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
)

我也喜欢制作一个foo/__main__.py,这使得Python可以将包作为模块执行,例如python3 -m foo将会将foo/__main__.py作为__main__执行。

some_directory/
    foo/
        __init__.py
        __main__.py  # `if __name__ == "__main__":`  lives here, `def main():` too!
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
    ...
    entry_points={
        'console_scripts': [
            # "foo" will be added to the installing-environment's text mode shell, eg `bash -c foo`
            'foo=foo.__main__:main',
        ]
    },
)

让我们用更多的模块来丰富一下: 基本上,你可以有这样的目录结构:

some_directory/
    bar.py           # `import bar`
    foo/
        __init__.py  # `import foo`
        __main__.py
        baz.py       # `import foo.baz
        spam/           
            __init__.py  # `import foo.spam`
            eggs.py      # `import foo.spam.eggs`
    setup.py

setup.py 一般包含源代码的元数据信息,例如:

  • 需要安装哪些依赖项(命名为 "install_requires")
  • 用于包管理的名称(安装/卸载名称),在我们的情况下建议与主要的 Python 包名称相匹配,即 foo,不过使用下划线代替连字符也很流行
  • 许可证信息
  • 成熟度标签(alpha/beta 等)
  • 受众标签(面向开发人员、面向机器学习等)
  • 单页文档内容(比如 README)
  • Shell 名称(用户 shell 类型的名称,如 bash,或者图形用户 shell 中类似于开始菜单的名称)
  • 此软件包将安装(和卸载)的 Python 模块列表
  • 事实上,"运行测试" 的入口点是 python ./setup.py test

它非常广泛,甚至可以在开发机器上安装源模块时即时编译 C 扩展。对于日常示例,我推荐 PYPA 示例存储库的 setup.py

如果您正在释放构建工件,例如一个副本代码,该副本被设计为在几乎相同的计算机上运行,那么 requirements.txt 文件是一种流行的方式来快照精确的依赖信息,其中 "install_requires" 是捕获最小和最大兼容版本的好方法。但是,考虑到目标计算机已经几乎相同,我强烈推荐创建整个 Python 前缀的 tarball。这可能有些棘手,这里不详细介绍。请查看 pip install--target 选项,或者了解 virtualenv 或 venv。

回到示例

如何从上层目录导入文件:

假设我们在 foo/spam/eggs.py 中,如果我们想要从 foo/baz 导入代码,我们可以通过其绝对命名空间来请求:

import foo.baz

如果我们希望将 eggs.py 移动到其他目录并使用其他相对于 baz 实现的方式,我们可以使用相对导入,例如: from . import baz
import ..baz

1
只是为了明确,您是说要使用import foo.bazimport ..baz,我需要在正确制作的setup.py文件上运行python3 -m pip install --editable FILEPATH吗?令人惊讶的是,Python需要这么多的开销来从上面进行导入... - JDG
1
我已经完成了所有这些步骤,现在我可以从任何计算机上的任何Python脚本中导入foo。是否有一种方法可以在没有importlib和所有那些样板文件的情况下从上面的文件进行导入? - JDG

6
这里是为了清晰起见,ThorSummoner的答案的三步骤版本,有点极简主义。它并不完全符合我的要求(我会在底部解释),但它还可以。
第一步:创建目录和setup.py文件。
filepath_to/project_name/
    setup.py

setup.py中编写:
import setuptools

setuptools.setup(name='project_name')

步骤二:将此目录安装为一个包

在控制台中运行以下代码:

python -m pip install --editable filepath_to/project_name

根据你的python安装方式,你可能需要使用python3或其他替代python。此外,你可以使用-e代替--editable

现在,你的目录看起来会像这样。我不知道那个egg是什么。

filepath_to/project_name/
    setup.py
    test_3.egg-info/
        dependency_links.txt
        PKG-INFO
        SOURCES.txt
        top_level.txt

这个文件夹被视为Python包,即使您在计算机的其他地方编写脚本,也可以从该父目录中的文件进行导入。
第三步:从上面进行导入
假设您创建了两个文件,一个在项目的主目录中,另一个在子目录中。它将如下所示:
filepath_to/project_name/
    top_level_file.py
    subdirectory/
        subfile.py

    setup.py          |
    test_3.egg-info/  |----- Ignore these guys
        ...           |

现在,如果top_level_file.py看起来像这样:
x = 1

然后我可以从 subfile.py 导入它,或者在计算机上的任何其他文件中导入。
# subfile.py  OR  some_other_python_file_somewhere_else.py

import random # This is a standard package that can be imported anywhere.
import top_level_file # Now, top_level_file.py works similarly.

print(top_level_file.x)

这与我想要的不同:我希望Python有一种一行导入上层文件的方法。相反,我必须像处理模块一样对待脚本,做大量样板工作,并全局安装它,以便整个Python安装都可以访问它。这太过繁琐了。如果有比上述过程或importlib花招更简单的方法,请告诉我。

你可以在虚拟环境中完成这个操作,而不是全局安装(使用 env\Scripts\python ...env/bin/python 命令来使用你的项目所使用的虚拟环境)。 - road_to_quantdom

4

使用 pathlib 的优化答案,引自@alex-martelli

import pathlib
import sys

_parentdir = pathlib.Path(__file__).parent.parent.resolve()
sys.path.insert(0, str(_parentdir))

import module_in_parent_dir

sys.path.remove(str(_parentdir))

2
运行 python /myprogram/submodule/mymodule.py,该模块导入了/myprogram/mainmodule.py,例如:通过以下方式
from mainmodule import *

在Linux上(例如,在python Docker镜像中),我必须将程序根目录添加到PYTHONPATH中:
export PYTHONPATH=/myprogram

2

现在是2022年,之前的回答都没有对我起作用。最终这个方法解决了我的问题。

import sys
sys.path.append('../my_class')
import my_class

我的目录结构:

src
--my_class.py
notebooks
-- mynotebook.ipynb

我从mynotebook.ipynb导入了my_class


0

您可以使用sys.path.append()方法将包所在的目录添加到搜索模块路径列表中。例如,如果该包位于当前目录的两个目录之上,则可以使用以下代码:

import sys
sys.path.append("../../")

如果软件包位于当前目录的上一级目录,则可以使用以下代码:
import sys
sys.path.append("..")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接