我试着通读关于兄弟姐妹导入的问题,甚至
软件包文档,但我还没找到答案。
结构如下:
├── 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
示例和测试目录中的脚本如何从
API模块和从命令行运行?
另外,我希望避免对每个文件都使用难看的sys.path.insert。肯定
这可以在Python中完成,对吧?
TLDR
这种方法不需要setuptools、路径hack、额外的命令行参数,也不需要在项目的每个文件中指定包的顶层。
只要在你调用的__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/ callsibb .py包含:
def CallSib():
print("Got Called")
if __name__ == '__main__':
CallSib()
如果你重现这个例子,你会注意到调用Main.py会导致“Got Called”被打印出来,就像在sib2/ callsibb .py中定义的那样,即使sib2/ callsibb .py是通过sib1/call.py调用的。但是,如果直接调用sib1/call.py(在对导入进行适当更改之后),则会抛出异常。即使它在其父目录中被脚本调用时可以工作,但如果它认为自己位于包的顶层,它就不能工作。
对于兄弟包导入,您可以使用[sys. xml]的insert或append方法。路径][2]模块:
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
你不需要也不应该侵入系统。路径,除非它是必要的,在这种情况下,它不是。使用:
import api.api_key # in tests, examples
从项目目录运行:python -m tests.test_one。
你可能应该把测试(如果它们是api的单元测试)移动到api内部并运行python -m api。测试运行所有测试(假设有__main__.py)或python -m api.test。Test_one来运行Test_one。
你也可以从示例中删除__init__.py(它不是Python包),并在安装api的virtualenv中运行示例,例如pip install -e。在virtualenv中,如果你有正确的setup.py,就会安装API包。