如何解决兄弟包导入
| 我尝试阅读有关同级进口甚至进口商品的问题 包文档,但我还没有找到答案。 具有以下结构:├── 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
hack。一定
这可以用Python完成,对吧?
解决方法
七年后
自从我在下面写下答案以来,修改ѭ5仍然是一种快速技巧,适用于私有脚本,但是有一些改进
安装软件包(无论是否在virtualenv中)都可以满足您的要求,尽管我建议使用pip而不是直接使用setuptools(并使用
setup.cfg
存储元数据)
使用-m
标志并作为软件包运行也可以(但如果要将工作目录转换为可安装的软件包,会有些尴尬)。
对于测试,尤其是pytest在这种情况下能够找到api包,并为您解决sys.path
的黑客问题
因此,这实际上取决于您要做什么。但是,对于您而言,由于您的目标似乎是在某个时候制作一个合适的软件包,因此即使不很完美,通过pip -e
安装也可能是您的最佳选择。
旧答案
正如其他地方已经提到的那样,可怕的事实是,您必须进行丑陋的修改,才能允许从同级模块或“ or10”模块的父级包中导入。 PEP 366中对此问题进行了详细说明。PEP3122试图以一种更合理的方式处理进口,但是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
\的父文件夹中)进行绝对导入。
,厌倦了sys.path hacks?
有许多ѭ15的-hacks,但我发现了另一种解决手头问题的方法:setuptools。我不确定是否存在无法很好解决的情况。以下内容已在Windows 10机器的Python 3.6.5(Anaconda,conda 4.5.1)中进行了测试。
设定
起点是您提供的文件结构,包装在名为“ 16”的文件夹中。
.
└── 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
我将把“ 18”称为根文件夹,在我的示例中,它位于“ 19”。
api.py
作为测试用例,让我们使用以下./api/api.py
def function_from_api():
return \'I am the return value from api.api!\'
test_one.py
from api.api import function_from_api
def test_function():
print(function_from_api())
if __name__ == \'__main__\':
test_function()
尝试运行test_one:
PS C:\\tmp\\test_imports> python .\\myproject\\tests\\test_one.py
Traceback (most recent call last):
File \".\\myproject\\tests\\test_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:\\tmp\\test_imports> python .\\myproject\\tests\\test_one.py
Traceback (most recent call last):
File \".\\tests\\test_one.py\",in <module>
from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package
脚步
1)将setup.py文件创建到根目录
setup.py
的内容为*
from setuptools import setup,find_packages
setup(name=\'myproject\',version=\'1.0\',packages=find_packages())
2)使用虚拟环境
如果您熟悉虚拟环境,请激活一个,然后跳到下一步。并非绝对需要使用虚拟环境,但从长远来看(当您正在进行多个项目时),它们确实可以帮助您。最基本的步骤是(在根文件夹中运行)
创建虚拟环境
python -m venv venv
激活虚拟环境
source ./venv/bin/activate
(Linux,macOS)或./venv/Scripts/activate
(Win)
要了解有关此内容的更多信息,只需在Google上搜索“ python虚拟环境教程”或类似内容即可。除了创建,激活和停用之外,您可能根本不需要任何其他命令。
创建并激活虚拟环境后,控制台应在括号中提供虚拟环境的名称。
PS C:\\tmp\\test_imports> python -m venv venv
PS C:\\tmp\\test_imports> .\\venv\\Scripts\\activate
(venv) PS C:\\tmp\\test_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
├── setup.py
└── venv
├── Include
├── Lib
├── pyvenv.cfg
└── Scripts [87 entries exceeds filelimit,not opening dir]
3)pip以可编辑状态安装项目
使用pip
安装顶级套件myproject
。诀窍是在安装时使用-e
标志。这样,它以可编辑状态安装,并且对.py文件所做的所有编辑将自动包含在已安装的软件包中。
在根目录中,运行
pip install -e .
(注意点,它表示\“当前目录\”)
您还可以使用pip freeze
看到已安装
(venv) PS C:\\tmp\\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\\tmp\\test_imports> pip freeze
myproject==1.0
4)在您的进口商品中加myproject.
请注意,您只需要在导入中添加myproject.
,否则将无法正常工作。没有setup.py
和pip install
的进口产品仍然可以正常工作。请参阅下面的示例。
测试解决方案
现在,让我们使用上面定义的api.py
和下面定义的test_one.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:\\tmp\\test_imports> python .\\myproject\\tests\\test_one.py
I am the return value from api.api!
*有关更多详细的setup.py示例,请参阅setuptools文档。
**实际上,您可以将虚拟环境放在硬盘上的任何位置。
, 这是我在tests
文件夹中的Python文件顶部插入的另一种选择:
# Path hack.
import sys,os
sys.path.insert(0,os.path.abspath(\'..\'))
, 除非有必要,否则您不需要并且不应该破解sys.path
,在这种情况下则不是。采用:
import api.api_key # in tests,examples
从项目目录中运行:python -m tests.test_one
。
您可能应该将tests
(如果它们是api的单元测试)移到api
内,然后运行python -m api.test
运行所有测试(假设有__main__.py
)或or55ѭ运行run56ѭ。
您也可以从examples
中删除__init__.py
(这不是Python软件包),然后在安装了api
的virtualenv中运行示例,例如,如果您有适当的setup.py
,则在virtualenv中的pip install -e .
会安装就地api
软件包。
, 我还没有对Python的理解,没有看到在兄弟姐妹/相对导入黑客之间不相关项目之间共享代码的预期方式所必需的。直到那天,这是我的解决方案。对于examples
或tests
从..\\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
, 对于同级包导入,可以使用[sys.path] [2]模块的insert或append方法:
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
,TLDR
此方法不需要setuptools,path hacks,其他命令行参数或在项目的每个文件中指定包的顶层。
只需在要调用的10父目录中创建一个脚本,然后从那里运行所有内容。有关进一步的说明,请继续阅读。
说明
这可以实现,而无需一起破解新路径,使用额外的命令行参数或向您的每个程序添加代码以识别其兄弟姐妹。
正如我之前提到的那样,此操作失败的原因是被调用的程序将其“ 73”设置为“ 10”。发生这种情况时,被调用的脚本会接受自身位于程序包的顶层,并拒绝识别兄弟目录中的脚本。
但是,目录顶层下的所有内容仍然可以识别顶层下的任何内容。这意味着,要使同级目录中的文件相互识别/利用,您只需要做的就是从其父目录中的脚本中调用它们。
概念证明
在具有以下结构的目录中:
.
|__Main.py
|
|__Siblings
|
|___sib1
| |
| |__call.py
|
|___sib2
|
|__callsib.py
Main.py
包含以下代码:
import sib1.call as call
def main():
call.Call()
if __name__ == \'__main__\':
main()
sib1 / call.py包含:
import sib2.callsib as callsib
def Call():
callsib.CallSib()
if __name__ == \'__main__\':
Call()
sib2 / callsib.py包含:
def CallSib():
print(\"Got Called\")
if __name__ == \'__main__\':
CallSib()
如果重现此示例,您将注意到调用Main.py
将导致按照“81ѭ的定义打印\” Got Called \“,即使sib2/callsib.py
是通过sib1/call.py
调用的。但是,如果要直接调用ѭ83(在对导入进行了适当的更改之后),则会引发异常。即使它由其父目录中的脚本调用时可以工作,但如果它认为自己位于程序包的顶层,则它将无法工作。
, 您需要查看如何在相关代码中编写导入语句。如果examples/example_one.py
使用以下导入语句:
import api.api
...然后,它希望项目的根目录位于系统路径中。
没有任何黑客(如您所说)来支持此操作的最简单方法是从顶层目录运行示例,如下所示:
PYTHONPATH=$PYTHONPATH:. python examples/example_one.py
, 以防万一有人在Eclipse上使用Pydev的情况出现在这里:您可以使用Project-> Properties并在左侧下方设置External Libraries,将同级的父路径(以及调用模块的父路径)添加为外部库文件夹。菜单Pydev-PYTHONPATH。然后,您可以从同级导入,例如G。 from sibling import some_class
。
, 我制作了一个示例项目来演示如何处理此问题,这确实是如上所述的另一个sys.path hack。 Python同级导入示例,该示例依赖于:
if __name__ == \'__main__\':
import os
import sys
sys.path.append(os.getcwd())
只要您的工作目录位于Python项目的根目录下,这似乎就非常有效。如果有人将其部署在实际的生产环境中,那么很高兴听到它是否也可以在这里正常工作。
, 首先,应避免使用与模块本身同名的文件。它可能会破坏其他进口。
导入文件时,首先解释器检查当前目录,然后搜索全局目录。
在examples
或tests
中,您可以致电:
from ..api import api
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。