兄弟包导入

2024-12-05 08:37:00
admin
原创
145
摘要:问题描述:我尝试阅读有关兄弟导入的问题甚至包文档,但我还没有找到答案。结构如下:├── LICENSE.md ├── README.md ├── api │   ├── __init__.py │   ├── api.py │   └── api_key.py ├── examples │   ├── __i...

问题描述:

我尝试阅读有关兄弟导入的问题甚至
包文档,但我还没有找到答案。

结构如下:

├── LICENSE.md
├── README.md
├── api
│   ├── __init__.py
│   ├── api.py
│   └── api_key.py
├── examples
│   ├── __init__.py
│   ├── example_one.py
│   └── example_two.py
└── tests
│   ├── __init__.py
│   └── test_one.py

examples和目录中的脚本如何 tests从模块导入
api并从命令行运行?

另外,我想避免sys.path.insert对每个文件进行丑陋的黑客攻击。这肯定可以用 Python 来完成,对吧?


解决方案 1:

厌倦了 sys.path 黑客吗?

有很多sys.path.append可用的 hack,但我找到了一种解决手头问题的替代方法。

概括

  • 将代码包装到一个文件夹中(例如packaged_stuff

  • 创建pyproject.toml文件来描述你的包(见pyproject.toml下面的最少内容)

  • Pip 使用以下可编辑状态安装包pip install -e <myproject_folder>

  • 导入使用from packaged_stuff.modulename import function_name


设置

起点是您提供的文件结构,包装在名为的文件夹中myproject

.
└── myproject
    ├── api
    │   ├── api_key.py
    │   ├── api.py
    │   └── __init__.py
    ├── examples
    │   ├── example_one.py
    │   ├── example_two.py
    │   └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

我将称之为.根文件夹,在我的示例中,它位于C: mp est_imports

api.py

作为测试用例,我们使用以下内容./api/api.py

def function_from_api():
    return 'I am the return value from api.api!'

测试一

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

尝试运行test_one:

PS C:    mp    est_imports> python .myproject    ests    est_one.py
Traceback (most recent call last):
  File ".myproject    ests    est_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

尝试相对导入也不起作用:

使用from ..api.api import function_from_api将导致

PS C:    mp    est_imports> python .myproject    ests    est_one.py
Traceback (most recent call last):
  File ".    ests    est_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

步骤

1)在根目录下创建一个 pyproject.toml 文件

(以前人们使用setup.py文件)

最小的内容pyproject.toml将是*

[project]
name = "myproject"
version = "0.1.0"
description = "My small project"

[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core >=3.2,<4"]

2)使用虚拟环境

如果您熟悉虚拟环境,请激活一个,然后跳到下一步。虚拟环境的使用不是绝对必要的,但从长远来看,它们确实会对您有所帮助(当您有多个正在进行的项目时……)。最基本的步骤是(在根文件夹中运行)

  • 创建虚拟环境

    • python -m venv venv

  • 激活虚拟环境

    • source ./venv/bin/activate(Linux、macOS)或./venv/Scripts/activate(Win)

要了解更多信息,只需在 Google 上搜索“python 虚拟环境教程”或类似内容。除了创建、激活和停用之外,您可能不需要任何其他命令。

创建并激活虚拟环境后,控制台应在括号中显示虚拟环境的名称

PS C:    mp    est_imports> python -m venv venv
PS C:    mp    est_imports> .envScriptsactivate
(venv) PS C:    mp    est_imports>

你的文件夹树应该看起来像这样**

.
├── myproject
│   ├── api
│   │   ├── api_key.py
│   │   ├── api.py
│   │   └── __init__.py
│   ├── examples
│   │   ├── example_one.py
│   │   ├── example_two.py
│   │   └── __init__.py
│   ├── LICENCE.md
│   ├── README.md
│   └── tests
│       ├── __init__.py
│       └── test_one.py
├── pyproject.toml
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]

3)pip 安装你的项目到可编辑状态

myproject使用安装顶级包pip。诀窍是-e在安装时使用标志。这样它就会以可编辑状态安装,并且对 .py 文件所做的所有编辑都将自动包含在已安装的包中。使用 pyproject.toml 和 -e 标志需要 pip >= 21.3

在根目录中运行

pip install -e .(注意点,它代表“当前目录”)

您还可以使用以下命令安装它pip freeze

Obtaining file:///home/user/projects/myproject
  Installing build dependencies ... done
  Checking if build backend supports build_editable ... done
  Getting requirements to build editable ... done
  Preparing editable metadata (pyproject.toml) ... done
Building wheels for collected packages: myproj
  Building editable for myproj (pyproject.toml) ... done
  Created wheel for myproj: filename=myproj-0.1.0-py2.py3-none-any.whl size=903 sha256=f19858b080d4e770c2a172b9a73afcad5f33f4c43c86e8eb9bdacbe50a627064
  Stored in directory: /tmp/pip-ephem-wheel-cache-qohzx1u0/wheels/55/5f/e4/507fdeb40cdef333e3e0a8c50c740a430b8ce84cbe17ae5875
Successfully built myproject
Installing collected packages: myproject
Successfully installed myproject-0.1.0
(venv) PS C:    mp    est_imports> pip freeze
myproject==0.1.0

4)添加myproject.到你的导入中

请注意,您只需要添加myproject.到原本无法工作的导入中。没有pyproject.toml& 的导入pip install仍可正常工作。请参阅下面的示例。


测试解决方案

api.py现在,让我们使用上面定义和下面定义来测试解决方案test_one.py

测试一

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

运行测试

(venv) PS C:    mp    est_imports> python .myproject    ests    est_one.py
I am the return value from api.api!

  • 这里使用 flit 作为构建后端。还有其他替代方案。

** 实际上,您可以将虚拟环境放在硬盘上的任何位置。

解决方案 2:

七年后

自从我在下面写下答案以来,修改sys.path仍然是一个快速而肮脏的技巧,适用于私人脚本,但已经有一些改进

  • 安装包(在虚拟环境中或不在虚拟环境中)将为您提供所需的内容,但我建议使用 pip 来执行此操作,而不是直接使用 setuptools(并使用setup.cfg来存储元数据)

  • 使用-m标志并作为包运行也是可行的(但如果您想将工作目录转换为可安装包,就会有点尴尬)。

  • 具体来说,对于测试来说,pytest能够在这种情况下找到 api 包并sys.path为你处理这些黑客问题

所以这真的取决于你想做什么。不过,就你的情况而言,由于你的目标是在某个时候制作一个合适的包,所以通过安装pip -e可能是你最好的选择,即使它还不完美。

旧答案

正如其他地方所述,可怕的事实是,您必须进行丑陋的黑客攻击才能允许从__main__模块的兄弟模块或父包导入。 该问题在PEP 366中有详细说明。PEP 3122试图以更合理的方式处理导入,但 Guido 拒绝了它

唯一的用例似乎是运行恰好位于模块目录中的脚本,我一直将其视为反模式。

(这里)

不过,我经常使用这种模式

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

path[0]是您正在运行的脚本的父文件夹和dir(path[0])顶层文件夹。

不过,我仍然无法使用相对导入,但它确实允许从顶层(在示例api的父文件夹中)进行绝对导入。

解决方案 3:

下面是我在文件夹中的 Python 文件顶部插入的另一种替代方法tests

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

解决方案 4:

sys.path除非必要,否则你不需要也不应该进行黑客攻击,而在这种情况下,情况并非如此。使用:

import api.api_key # in tests, examples

从项目目录运行:python -m tests.test_one

您可能应该移动tests(如果它们是 api 的单元测试)到内部api并运行python -m api.test以运行所有测试(假设有__main__.py)或者python -m api.test.test_one运行test_one

您还可以__init__.py从中删除examples(它不是一个 Python 包)并在安装了的虚拟环境中运行示例api,例如,如果您有适当的,pip install -e .虚拟环境中将安装就地包。api`setup.py`

解决方案 5:

对于 2023 年的读者:如果您对以下内容没有信心pip install -e

TL;DR:脚本(通常是入口点)只能执行import与其级别相同或低于其级别的任何操作。

考虑这种层次结构,正如Python 3 中的相对导入的答案所建议的那样:

MyProject
├── src
│   ├── bot
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── sib1.py
│   └── mod
│       ├── __init__.py
│       └── module1.py
└── main.py

为了使用简单命令从起点运行我们的程序python main.py,我们在这里使用绝对导入(没有前导点)main.py

from src.bot import main


if __name__ == '__main__':
    main.magic_tricks()

的内容bot/main.py利用显式相对导入来显示我们正在导入的内容,如下所示:

from .sib1 import my_drink                # Both are explicit-relative-imports.
from ..mod.module1 import relative_magic

def magic_tricks():
    # Using sub-magic
    relative_magic(in=["newbie", "pain"], advice="cheer_up")
    
    my_drink()
    # Do your work
    ...

理由如下:

  • 说实话,当我们想要运行我们的 Python 程序时,我们不想给出“好的,所以这是一个模块”的提示。

    • 因此我们使用绝对导入作为入口点main.py,这样我们就可以通过简单的方式运行我们的程序python main.py

    • 在后台,Python 将用来为我们解析包,但是这也意味着,由于try中的路径顺序,sys.path我们要导入的包可能会被任何其他同名包取代sys.path`import test`

  • 为了避免这些冲突,我们使用显式相对导入

    • 语法from ..mod非常清楚地表明“我们正在导入我们自己的本地包”。

    • 但是缺点是,当您想将模块作为脚本运行时,您需要再次考虑“好的,这是一个模块”。

  • 最后,from ..mod 部分意味着将上升一级MyProject/src

结论

  1. main.py脚本放在所有包的根目录旁边MyProject/src,并使用绝对导入python main.py来导入任何内容。没有人会创建名为的包src

  2. 那些明确的相对导入将会起作用。

  3. 要运行模块,请使用python -m ...

src/附录:有关以脚本形式运行任何文件的更多信息?

然后你应该使用语法python -m 并看看我的另一篇文章:ModuleNotFoundError:没有名为“sib1”的模块

解决方案 6:

我还不具备足够的 Pythonology 知识,无法理解在不相关项目之间共享代码的预期方式,而无需使用兄弟/相对导入 hack。直到那一天,这是我的解决方案。对于examplestests从导入内容..api,它看起来像:

import sys.path
import os.path
# Import from sibling directory ..api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

解决方案 7:

对于兄弟包导入,您可以使用sys.path模块的插入附加方法:

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

如果您按如下方式启动脚本,则此方法有效:

python examples/example_one.py
python tests/test_one.py

另一方面,您也可以使用相对导入:

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

在这种情况下,您必须使用“-m”参数启动脚本(请注意,在这种情况下,您不能给出“.py”扩展名):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

当然,你可以混合这两种方法,这样无论如何调用你的脚本都可以工作:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

解决方案 8:

您需要查看相关代码中的 import 语句是如何编写的。如果examples/example_one.py使用以下 import 语句:

import api.api

...然后它期望项目的根目录位于系统路径中。

支持此功能的最简单方法(无需任何黑客攻击,正如您所说的)是从顶级目录运行示例,如下所示:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

解决方案 9:

以防万一有人在 Eclipse 上使用 Pydev 最终出现在这里:您可以使用Project->Properties并在左侧菜单Pydev-PYTHONPATH下设置外部库,将兄弟的父路径(以及调用模块的父路径)添加为外部库文件夹。然后,您可以从兄弟那里导入,例如。from sibling import some_class

解决方案 10:

我想对np8 提供的解决方案发表评论,但我的声誉不够,所以我只想提一下,您可以完全按照他们的建议创建一个 setup.py 文件,然后pipenv install --dev -e .从项目根目录执行操作以将其转换为可编辑的依赖项。然后您的绝对导入将起作用,例如from api.api import foo,您不必处理系统范围的安装。

文档

解决方案 11:

如果您正在使用 pytest,那么pytest 文档描述了如何从单独的测试包引用源包的方法。

建议的项目目录结构是:

setup.py
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

文件内容setup.py

from setuptools import setup, find_packages

setup(name="PACKAGENAME", packages=find_packages())

以可编辑模式安装软件包:

pip install -e .

pytest 文章引用了Ionel Cristian Mărieș 的这篇博客文章。

解决方案 12:

我制作了一个示例项目来演示如何处理这个问题,这实际上是如上所述的另一个 sys.path 黑客。Python Sibling Import Example,它依赖于:

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

只要你的工作目录仍然位于 Python 项目的根目录,这似乎非常有效。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用