如何导入上面目录中的Python类?

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

是否可以相对导入该文件?

321579 次浏览

from ..subpkg2 import mod

根据Python文档:当在包层次结构中,使用两个点,如导入语句文档所示:

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

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

@gimel的答案是正确的如果你可以保证他提到的包层次。如果你不能——如果你的真正需求如你所表达的那样,只与目录绑定,与打包没有任何必要的关系——那么你需要在__file__上工作,以找出父目录(几个os.path.dirname调用就可以了;-),然后(如果该目录还没有在sys.path上)临时插入sys.path__import__的开头,再次删除该dir——确实很麻烦,但是,“当你必须这样做时,你必须”(Pyhon努力从不阻止程序员做必须应该做的事情——就像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
import sys
sys.path.append("..") # Adds higher directory to python modules path.

从一个恰好比当前目录高一级的目录导入模块:

from .. import module

Python是一个模块化的系统

Python不依赖于文件系统

要可靠地加载python代码,请将该代码放在一个模块中,并将该模块安装在python的库中。

安装的模块总是可以用import <name>从顶层命名空间加载


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

基本上,你可以有一个这样的目录结构:

the_foo_project/
setup.py


bar.py           # `import bar`
foo/
__init__.py    # `import foo`


baz.py         # `import foo.baz`


faz/           # `import foo.faz`
__init__.py
daz.py       # `import foo.faz.daz` ... etc.

一定要在setup.py中声明你的setuptools.setup()

官方示例:https://github.com/pypa/sampleproject/blob/master/setup.py

在本例中,我们可能想导出bar.pyfoo/__init__.py,我的简单例子:

setup . py

#!/usr/bin/env python3


import setuptools


setuptools.setup(
...
py_modules=['bar'],
packages=['foo'],
...
entry_points={},
# Note, any changes to your setup.py, like adding to `packages`, or
# changing `entry_points` will require the module to be reinstalled;
# `python3 -m pip install --upgrade --editable ./the_foo_project
)

现在我们可以将模块安装到python库中; 使用pip,你可以在编辑模式下将the_foo_project安装到python库中, 所以我们可以实时处理它

python3 -m pip install --editable=./the_foo_project


# if you get a permission error, you can always use
# `pip ... --user` to install in your user python library

现在,我们可以从任何python上下文中加载共享的py_modules和包

foo_script.py

#!/usr/bin/env python3


import bar
import foo


print(dir(bar))
print(dir(foo))

如何加载一个模块,这是一个目录

前言:我对之前的回答做了大量的重写,希望能帮助人们轻松地进入python的生态系统,并希望能给每个人带来python导入系统的最佳成功。

这将涵盖包中的相对导入,我认为这是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扩展,甚至远程代码定义加载。

因此,如果导入没有耦合到文件系统,那么“一个目录向上”;的意思吗?我们必须选择一些启发式,但我们可以这样做,例如,当在中工作时,一些启发式已经定义,使相对导入,如__ABC0和..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/
< p >“editable"又名-e将再次重定向导入机器以加载此目录中的源文件,而不是将当前文件复制到安装环境的库中。这也会导致开发人员机器上的行为差异,一定要测试您的代码! 除了pip,还有其他工具,但我建议使用pip作为介绍性工具:)

我也喜欢让foo成为一个“package”;(包含__init__.py的目录)而不是模块(单个".py"文件),都是“包”;和“;modules"可以加载到根命名空间,模块允许嵌套的命名空间,这是有帮助的,如果我们想有一个&;相对的一个目录up&;导入。

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将作为__main__执行foo/__main__.py

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"
  • 应该使用什么名称来进行包管理(安装/卸载"name"),我建议这个与您的主要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

如果我们想保留将来使用其他相对baz实现将eggs.py移动到其他目录的能力,我们可以使用类似这样的相对导入:

import ..baz

为了清晰起见,这里有一个三步,有点简单的ThorSummoner的答案。它不完全是我想要的(我将在底部解释),但它工作正常。

步骤1:创建directory和setup.py

filepath_to/project_name/
setup.py

setup.py中,写:

import setuptools


setuptools.setup(name='project_name')

步骤2:将该目录作为包安装

在控制台运行以下代码:

python -m pip install --editable filepath_to/project_name

而不是python,你可能需要使用python3或其他,这取决于你的python是如何安装的。同样,你可以使用-e代替--editable

现在,您的目录将或多或少像这样。我不知道鸡蛋是什么东西。

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

这个文件夹被认为是一个python包,即使你在电脑的其他地方写脚本,你也可以从这个父目录中的文件导入。

步骤3。从上面输入

假设您创建了两个文件,一个在项目的主目录中,另一个在子目录中。它看起来是这样的:

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恶作剧,请告诉我。

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))

运行导入/myprogram/mainmodule.pypython /myprogram/submodule/mymodule.py,例如via

from mainmodule import *

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

export PYTHONPATH=/myprogram