如何从另一个目录导入Python包?

31

我有一个按以下结构构建的项目:

project
├── api
│   ├── __init__.py
│   └── api.py
├── instance
│   ├── __init__.py
│   └── config.py
├── package
│   ├── __init__.py
│   └── app.py
├── requirements.txt
└── tests
    └── __init__.py

我正试图从package/app.py中调用config.py文件,如下所示:

# package/app.py
from instance import config

# I've also tried
import instance.config
import ..instance.config
from ..instance import config

但我总是收到以下错误:

Traceback (most recent call last):
  File "/home/csymvoul/projects/project/package/app.py", line 1, in <module>
    from instance import config
ModuleNotFoundError: No module named 'instance'

修改sys.path不是我想做的事情。我知道这个问题已经有了非常多的答案,但是给出的答案对我来说都不起作用。

编辑:当将app.py移动到根目录时,它可以正常工作。但我需要将它放在package文件夹下。


我也查看了这个 https://dev59.com/Rmw15IYBdhLWcg3wWqZ0 但似乎没有起作用。 - csymvoul
2个回答

28

您可以将父目录添加到PYTHONPATH中,为此,您可以在“模块搜索路径”中使用依赖于操作系统的路径(列在sys.path中)。因此,您可以像下面这样轻松地添加父目录:

import sys
sys.path.insert(0, '..')

from instance import config

请注意,前面的代码使用了相对路径,因此您必须在同一位置内启动该文件,否则可能无法正常工作。要从任何地方启动,可以使用pathlib模块。

from pathlib import Path
import sys
path = str(Path(Path(__file__).parent.absolute()).parent.absolute())
sys.path.insert(0, path)

from instance import config

然而,以前的方法更像是一种hack,为了做正确的事情,您首先需要按照这篇非常详细的博客文章python packaging重塑项目结构,采用src文件夹的推荐方式。

  • 您的目录布局必须如下所示:
project
├── CHANGELOG.rst
├── README.rst
├── requirements.txt
├── setup.py
├── src
│   ├── api
│   │   ├── api.py
│   │   └── __init__.py
│   ├── instance
│   │   ├── config.py
│   │   └── __init__.py
│   └── package
│       ├── app.py
│       └── __init__.py
└── tests
    └── __init__.py

请注意,您实际上不需要 requirements.txt,因为您可以在 setup.py 中声明依赖项。 这是一个示例 setup.py(改编自 这里):

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function

import io
import re
from glob import glob
from os.path import basename
from os.path import dirname
from os.path import join
from os.path import splitext

from setuptools import find_packages
from setuptools import setup


def read(*names, **kwargs):
    with io.open(
        join(dirname(__file__), *names),
        encoding=kwargs.get('encoding', 'utf8')
    ) as fh:
        return fh.read()


setup(
    name='nameless',
    version='1.644.11',
    license='BSD-2-Clause',
    description='An example package. Generated with cookiecutter-pylibrary.',
    author='mpr',
    author_email='contact@ionelmc.ro',
    packages=find_packages('src'),
    package_dir={'': 'src'},
    include_package_data=True,
    zip_safe=False,
    classifiers=[
        # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: Unix',
        'Operating System :: POSIX',
        'Operating System :: Microsoft :: Windows',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: Implementation :: PyPy',
        # uncomment if you test on these interpreters:
        # 'Programming Language :: Python :: Implementation :: IronPython',
        # 'Programming Language :: Python :: Implementation :: Jython',
        # 'Programming Language :: Python :: Implementation :: Stackless',
        'Topic :: Utilities',
    ],
    keywords=[
        # eg: 'keyword1', 'keyword2', 'keyword3',
    ],
    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
    install_requires=[
        # eg: 'aspectlib==1.1.1', 'six>=1.7',
    ],
    extras_require={
        # eg:
        #   'rst': ['docutils>=0.11'],
        #   ':python_version=="2.6"': ['argparse'],
    },
    setup_requires=[
        # 'pytest-runner',
    ],
    entry_points={
        'console_scripts': [
            'api = api.api:main',
        ]
    },
)

我的api.py文件的内容:

from instance import config

def main():
    print("imported")
    config.config()

我的 config.py 文件的内容:

def config():
    print("config imported successfully")

你可以在此处找到所有之前的内容

  • 可选但建议:创建一个虚拟环境,我使用venv(对于Python 3.3 <=),在项目的根目录中:
python -m venv .

并激活:

source bin/activate
  • 现在我可以安装这个软件包:

在项目根目录下使用pip install -e .(注意有点号)命令来进行安装。

  • from instance import config的导入现在已经可以工作了,您可以通过运行api.py文件来确认:
python src/api/api.py

第二个选项完全可行,但是有没有更简单的方法来实现它而不必干扰路径? - csymvoul
为了使用更简单的东西,您需要调整您的目录结构,请查看Python打包 - RMPR
你在项目根目录下创建了 setup.py 文件吗? - RMPR
我的脚本有更多嵌套的文件夹,其中包含不同的.py文件。 如果我安装并运行它,我会收到如下错误:File "cli.py", line 20, in <module> from lib import utils ModuleNotFoundError: No module named 'lib'。我的一个文件夹叫做lib,确实包含utils.py,我该如何解决这个问题? - Pitto
你的项目结构是什么?如果文件散落在这里那里,你可能需要按照答案建议的方式将它们放置,然后继续进行。 - RMPR
显示剩余4条评论

1
从Python 3.3开始,您不需要在子目录中使用__init__.py文件进行导入。实际上,拥有它们可能会产生误导,因为它会在每个包含init文件的文件夹中创建包命名空间,如此处所述。
通过删除所有这些__init__.py文件,您将能够在运行app.py时在命名空间package(包括子目录)中导入文件,但这仍然不是我们想要的。
Python解释器仍然不知道如何访问您的instance命名空间。为了做到这一点,您可以使用PYTHONPATH环境变量,其中包括一个父路径config.py。您可以像@RMPR的答案中建议的那样使用sys.path,或者直接设置环境变量,例如:
PYTHONPATH=/home/csymvoul/projects/project python3 /home/csymvoul/projects/project/package/app.py

然后像这样导入依赖项:from instance import config


你提到的 PYTHONPATH 在终端中运行时确实有效。虽然这不是本题的问题,但我想请问如何将这个 PYTHONPATH 添加到 VS Code 中,以便将根目录视为路径? - csymvoul
您可以将环境变量添加为终端的配置,例如在您的settings.json文件中: "terminal.integrated.env.linux": { "PYTHONPATH": "${workspaceFolder}" } - stjernaluiht
命令“Preferences: Open workspace settings (JSON)”将直接带您进入设置文件。请注意,这只会在本地设置PYTHONPATH变量,如果您在另一个环境中部署代码,则需要设置它。这是来自VSCode的相关文档页面:https://code.visualstudio.com/docs/python/environments#_use-of-the-pythonpath-variable - stjernaluiht
在Python 3.3及以上版本中,除非您有非常特定的用例(如此处所述),否则仍需要将__init__.py文件添加到项目子目录中。这在Python 3.10+中仍然适用。 - ianyoung

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