我试过阅读有关兄弟进口甚至是兄弟姐妹的问题 package documentation,但我还没有找到答案。
具有以下结构:
├── 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中完成,对吧?
答案 0 :(得分:62)
有很多sys.path.append
- 可用,但我找到了解决问题的另一种方法:setuptools。我不确定是否存在与此无关的边缘情况。以下是使用Python 3.6.5(Anaconda,conda 4.5.1),Windows 10计算机进行测试的。
起点是您提供的文件结构,包含在名为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:\tmp\test_imports\
。
作为测试用例,请使用以下内容./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()
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", line 1, in <module>
from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package
setup.py
的内容为*
from setuptools import setup, find_packages
setup(name='myproject', version='1.0', packages=find_packages())
如果您熟悉虚拟环境,请激活一个,然后跳到下一步。虚拟环境的使用不是绝对,但它们将真的帮助你从长远来看(当你有超过1个项目正在进行..)。最基本的步骤是(在根文件夹中运行)
python -m venv venv
source ./venv/bin/activate
(Linux,macOS)或./venv/Scripts/activate
(Win)要了解有关此内容的更多信息,请参阅Google的&#34; python虚拟环境教程&#34;或类似的。除了创建,激活和停用之外,您可能永远不需要任何其他命令。
制作并激活虚拟环境后,您的控制台应在括号中提供虚拟环境的名称
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]
使用myproject
安装顶级包pip
。诀窍是在安装时使用-e
标志。这样,它以可编辑状态安装,对.py文件所做的所有编辑将自动包含在已安装的包中。
在根目录中,运行
pip install -e .
(注意点,它代表&#34;当前目录&#34;)
您还可以使用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
myproject.
添加到您的导入请注意,您必须仅将myproject.
添加到无法正常工作的导入中。没有setup.py
&amp; 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:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!
*有关详细的setup.py示例,请参阅setuptools docs。
**实际上,您可以将虚拟环境放在硬盘上的任何位置。
答案 1 :(得分:44)
由于我在下面写了答案,修改sys.path
仍然是一个快速而肮脏的技巧,适用于私有脚本,但有一些改进
setup.cfg
来存储元数据)-m
flag并且作为一个包也可以运行(但如果你想将你的工作目录转换为可安装的包,会变得有点尴尬)。sys.path
黑客攻击所以这真的取决于你想做什么。但是,在你的情况下,因为你的目标似乎是在某个时候制作一个合适的包,所以通过pip -e
进行安装可能是你最好的选择,即使它还不完美。
正如其他地方已经说过的那样,可怕的事实是你必须做丑陋的黑客攻击才能允许从__main__
模块中导入兄弟模块或父包。该问题详见PEP 366。 PEP 3122试图以更合理的方式处理进口,但Guido拒绝了
唯一的用例似乎是运行脚本 住在模块的目录中,我一直认为这是一个目录 反模式。
(here)
尽管如此,我会定期使用此模式
# 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
的父文件夹中)。
答案 2 :(得分:38)
这是我在tests
文件夹中的Python文件顶部插入的另一种选择:
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
答案 3 :(得分:23)
除非有必要,否则你不需要也不应该攻击sys.path
,在这种情况下它不是。使用:
import api.api_key # in tests, examples
从项目目录运行:python -m tests.test_one
。
您应该在tests
内移动api
(如果它们是api的单元测试)并运行python -m api.test
以运行所有测试(假设有__main__.py
)或{{1而是运行python -m api.test.test_one
。
你也可以从test_one
删除__init__.py
(它不是Python包)并在安装了examples
的virtualenv中运行示例,例如,api
如果您有适当的pip install -e .
,virtualenv会安装in api
包。
答案 4 :(得分:7)
我还没有必要理解Pythonology,以便在没有兄弟/相对导入黑客的情况下看到在不相关项目之间共享代码的预期方式。直到那天,这是我的解决方案。要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
答案 5 :(得分:3)
对于兄弟姐妹包导入,您可以使用 [sys.path] [2] 模块的 insert 或追加方法:
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' argument启动脚本(请注意,在这种情况下,您不得提供'。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
答案 6 :(得分:1)
您需要查看如何在相关代码中编写import语句。如果examples/example_one.py
使用以下import语句:
import api.api
...然后它希望项目的根目录位于系统路径中。
最简单的方法是在没有任何黑客攻击的情况下支持(如你所说)将从顶级目录运行示例,如下所示:
PYTHONPATH=$PYTHONPATH:. python examples/example_one.py
答案 7 :(得分:1)
以防有人在Eclipse上使用Pydev结束:你可以使用 Project-&gt ;属性并在左侧菜单 Pydev-PYTHONPATH 下设置外部库。然后你可以从兄弟姐妹那里导入,例如。 G。 from sibling import some_class
。
答案 8 :(得分:1)
TLDR
此方法不需要setuptools,path hacks,其他命令行参数或在项目的每个文件中指定软件包的顶层。
只需在要调用的__main__
父目录中创建一个脚本,然后从那里运行所有内容。有关更多说明,请继续阅读。
说明
这可以实现,而无需一起破解新路径,使用额外的命令行参数或向您的每个程序添加代码以识别其同级兄弟。
正如我之前提到的那样,此操作失败的原因是被调用的程序将其__name__
设置为__main__
。发生这种情况时,被调用的脚本会接受自身位于程序包的顶层,并拒绝识别兄弟目录中的脚本。
但是,目录顶层下的所有内容仍会识别顶层下的任何其他内容。这意味着要使同级目录中的文件能够识别/利用彼此,要做的唯一操作是从父目录中的脚本中调用它们。
概念证明 在具有以下结构的目录中:
.
|__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
是通过{{1}进行调用的,调用sib2/callsib.py
也会导致按照sib2/callsib.py
的定义打印“ Got Called”。 }。但是,如果要直接调用sib1/call.py
(在对导入进行适当更改之后),它将引发异常。即使该脚本在其父目录中的脚本调用时有效,但如果它认为自己位于程序包的顶层,则它将无法正常工作。
答案 9 :(得分:1)
我制作了一个示例项目来演示如何处理此问题,这确实是如上所述的另一个sys.path hack。 Python Sibling Import Example,取决于:
if __name__ == '__main__':
import os
import sys
sys.path.append(os.getcwd())
这似乎很有效,只要您的工作目录保留在Python项目的根目录下即可。如果有人将其部署在实际的生产环境中,那么很高兴知道它在这里也可以正常工作。
答案 10 :(得分:1)
如果您使用的是 pytest,那么 pytest docs 描述了一种如何从单独的测试包中引用源包的方法。
建议的项目目录结构是:
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 文章引用了 this blog post by Ionel Cristian Mărieș。
答案 11 :(得分:0)
我想评论np8提供的solution,但是我没有足够的声誉,因此我只想提一下,您可以按照他们的建议完全创建setup.py文件,然后执行{{ 1}},将其转换为可编辑的依赖项。然后您的绝对导入将起作用例如pipenv install --dev -e .
,您不必搞乱系统范围的安装。
答案 12 :(得分:-1)
1.1 用户
1.1.1 about.py
1.1.2 初始化 .py
1.2 技术
1.2.1 info.py
1.1.2 初始化 .py
现在,如果要访问用户包中的 about.py 模块,请从 info.py 技术包中的em> 模块,那么您必须将cmd(在Windows中)路径带到Project中,即 ** C:\ Users \ Personal \ Desktop \ Project> **,如上述Package示例所示。在此路径中,您必须输入 python -m Package_name.module_name 例如,对于上述包,我们必须要做的
C:\ Users \ Personal \ Desktop \ Project> python -m Tech.info
展示点
答案 13 :(得分:-1)
主要问题:
将同级文件夹作为模块:
从..导入同级文件夹
从同级文件夹中调用a_file.py作为模块:
从..siblingfolder导入a_file
在同级文件夹中的文件内调用a_function作为模块:
from..siblingmodule.a_file导入func_name_exists_in_a_file
最简单的方法。
转到lib / site-packages文件夹。
如果存在“ easy_install.pth”文件,则只需对其进行编辑并添加目录,即可将其作为模块添加到脚本中。
如果不存在,则将其设置为一个...然后将所需的文件夹放在那里
添加后...,python将自动将其视为类似于站点包的文件夹,并且您可以将该文件夹或子文件夹中的每个脚本作为模块调用。
我是用手机写的,很难设置它以使每个人都容易阅读。
答案 14 :(得分:-1)
在您的主文件中添加:
import sys
import os
sys.path.append(os.path.abspath(os.path.join(__file__,mainScriptDepth)))
mainScriptDepth = 主文件到项目根目录的深度。
这是您的情况 mainScriptDepth = "../../"
。然后,您可以通过指定项目根目录的路径 (from api.api import *
) 进行导入。
答案 15 :(得分:-3)
首先,您应该避免使用与模块本身同名的文件。它可能会打破其他进口。
导入文件时,首先解释器检查当前目录,然后搜索全局目录。
在examples
或tests
内,您可以致电:
from ..api import api