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

Posted

技术标签:

【中文标题】如何从另一个目录导入 python 包?【英文标题】:How to Import python package from another directory? 【发布时间】:2020-07-28 18:28:34 【问题描述】:

我有一个结构如下的项目:

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 文件夹下。

【问题讨论】:

我也检查了这个***.com/questions/6670275/…,但它似乎没有用 【参考方案1】:

您可以将父目录添加到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(改编自here):

#!/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")

你可以找到以前的所有here

可选但推荐:创建一个虚拟环境,我在项目的根目录中使用venv (Python 3.3
python -m venv .

然后激活:

source bin/activate
现在我可以安装包了:

在项目的根目录中使用pip install -e .(带点)命令

您的导入 from instance import config 现在可以使用,以确认您可以运行 api.py:
python src/api/api.py

【讨论】:

第二个选项工作得很好,但是没有更简单的方法可以在不弄乱路径的情况下实现它吗? 要使用更简单的东西,你需要调整你的目录结构,看看python packaging 据我了解,当我在目录中添加 __init__.py 时,python 将其识别为模块。为什么现在不是这样?我可以调整项目的结构,但TBH 除了这个src 目录与我当前的结构之外,我看不出有什么不同。除非您建议将它们全部放在一个文件夹下 以 src/ 开头的结构明确旨在不允许您将 __init__.py 文件放在 src/ 文件夹中。答案已更新。 您的建议似乎对我不起作用。运行pip install -e . 命令时出现此错误:Obtaining file:///home/csymvoul/projects/project ERROR: Files/directories not found in /home/csymvoul/projects/project/。请注意,我已经在使用虚拟环境【参考方案2】:

从 Python 3.3 开始,您的子目录中不需要 __init__.py 文件来进行导入。拥有它们实际上可能会产生误导,因为它会导致在包含初始化文件的每个文件夹中创建包命名空间,如 here 所述。

通过删除所有__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 添加到 VSCode 以便将根文件夹视为路径? 您可以像这样在 settings.json 中添加环境变量作为集成终端的配置:“terminal.integrated.env.linux”:“PYTHONPATH”:“$workspaceFolder” 命令“首选项:打开工作区设置 (JSON)”将带您直接进入设置文件。请注意,这只会在本地设置 PYTHONPATH 变量,如果您在另一个环境中部署代码,则需要设置它。这是来自 VSCode 的相关 docs page。

以上是关于如何从另一个目录导入 python 包?的主要内容,如果未能解决你的问题,请参考以下文章

如何以透明的方式从另一个 git 分支获取目录的副本?

如何将 python 函数从另一个文件导入 django 视图

将Python导入函数从另一个目录导入文件,然后从另一个目录导入另一个函数

从另一个目录导入的Python失败

从另一个目录导入python模块

使用 Python 3 从另一个目录中的模块导入本地函数,并在 Jupyter Notebook 中进行相对导入