zac*_*ill 149 python packages siblings python-import
我已经尝试阅读有关兄弟进口甚至 包文档的问题,但我还没有找到答案.
具有以下结构:
??? 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
Run Code Online (Sandbox Code Playgroud)
如何在脚本 examples
和tests
目录从导入
api
模块,并可以从命令行运行?
此外,我想避免sys.path.insert
每个文件的丑陋黑客.当然这可以在Python中完成,对吧?
np8*_*np8 102
有很多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
Run Code Online (Sandbox Code Playgroud)
我将调用.
根文件夹,在我的示例中,它位于C:\tmp\test_imports\
.
作为测试用例,让我们使用以下./api/api.py
def function_from_api():
return 'I am the return value from api.api!'
Run Code Online (Sandbox Code Playgroud)
from api.api import function_from_api
def test_function():
print(function_from_api())
if __name__ == '__main__':
test_function()
Run Code Online (Sandbox Code Playgroud)
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'
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
setup.py
将是*的内容
from setuptools import setup, find_packages
setup(name='myproject', version='1.0', packages=find_packages())
Run Code Online (Sandbox Code Playgroud)
如果您熟悉虚拟环境,请激活一个,然后跳到下一步.虚拟环境的使用并非绝对必要,但从长远来看,它们将真正帮助您(当您有超过1个项目正在进行时......).最基本的步骤是(在根文件夹中运行)
python -m venv venv
source ./venv/bin/activate
(Linux)或./venv/Scripts/activate
(Win)要了解更多相关信息,只需谷歌"python虚拟环境教程"或类似内容.除了创建,激活和停用之外,您可能永远不需要任何其他命令.
制作并激活虚拟环境后,控制台应在括号中指定虚拟环境的名称
PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
Run Code Online (Sandbox Code Playgroud)
你的文件夹树应该是这样的**
.
??? 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]
Run Code Online (Sandbox Code Playgroud)
安装您的顶级包myproject
使用pip
.诀窍是-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
Run Code Online (Sandbox Code Playgroud)
myproject.
到您的导入请注意,您myproject.
只需要添加到其他方式无效的导入中.没有setup.py
&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()
Run Code Online (Sandbox Code Playgroud)
(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!
Run Code Online (Sandbox Code Playgroud)
*有关更详细的setup.py示例,请参阅setuptools文档.
**实际上,您可以将虚拟环境放在硬盘上的任何位置.
Evp*_*pok 60
由于我在下面写了答案,修改sys.path
仍然是一个快速而肮脏的技巧,适用于私有脚本,但有几个改进
setup.cfg
用于存储元数据)-m
标志并作为包运行也是有效的(但如果要将工作目录转换为可安装的包,则会变得有点尴尬).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
Run Code Online (Sandbox Code Playgroud)
这path[0]
是您运行脚本的父文件夹和dir(path[0])
顶级文件夹.
尽管如此,我仍然无法使用相对导入,但它确实允许从顶层进行绝对导入(在示例api
的父文件夹中).
Cen*_*lti 39
这是我在文件tests
夹中的Python文件顶部插入的另一种替代方法:
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
Run Code Online (Sandbox Code Playgroud)
jfs*_*jfs 28
sys.path
除非有必要,否则你不需要也不应该破解,在这种情况下它不是.使用:
import api.api_key # in tests, examples
Run Code Online (Sandbox Code Playgroud)
从项目目录运行: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包)中删除并运行virtualenv中的示例,api
例如,如果你有正确pip install -e .
的virtualenv将安装inplace api
包setup.py
.
s̮̦*_*̥̳̼ 11
pip install -e
:TL;DR:脚本(通常是入口点)只能执行import
与其级别相同或低于其级别的任何内容。
考虑这个层次结构,正如Python 3 中的相对导入的答案所建议的那样:
\nMyProject\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src\n\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 bot\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 main.py\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 sib1.py\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 mod\n\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 module1.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.py\n\n
Run Code Online (Sandbox Code Playgroud)\n要使用简单的命令从头开始运行我们的程序python main.py
,我们在此处使用绝对导入(无前导点)main.py
:
MyProject\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src\n\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 bot\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 main.py\n\xe2\x94\x82 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 sib1.py\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 mod\n\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 module1.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.py\n\n
Run Code Online (Sandbox Code Playgroud)\n的内容bot/main.py
利用显式相对导入来显示我们正在导入的内容,如下所示:
from .sib1 import my_drink # Both are explicit-relative-imports.\nfrom ..mod.module1 import relative_magic\n\ndef magic_tricks():\n # Using sub-magic\n relative_magic(in=["newbie", "pain"], advice="cheer_up")\n \n my_drink()\n # Do your work\n ...\n
Run Code Online (Sandbox Code Playgroud)\n理由如下:
\nmain.py
,这样我们就可以简单地运行我们的程序python main.py
。sys.path
为我们解析包,但这也意味着我们想要导入的包可能会被任何其他同名包取代sys.path
,因为例如 try中的路径顺序import test
。from ..mod
非常清楚地表明“我们正在导入我们自己的本地包”。from ..mod
部分意味着它将上升一级到MyProject/src
。main.py
脚本放在所有包的根目录旁边MyProject/src
,并使用绝对导入python main.py
来导入任何内容。没有人会创建一个名为src
.python -m ...
.src/
脚本运行的更多信息?那么你应该使用语法python -m
并看看我的另一篇文章:ModuleNotFoundError: No module named \'sib1\'
小智 9
我还没有必要理解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
Run Code Online (Sandbox Code Playgroud)
对于兄弟包导入,您可以使用[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
Run Code Online (Sandbox Code Playgroud)
如果您按如下方式启动脚本,这将起作用:
python examples/example_one.py
python tests/test_one.py
Run Code Online (Sandbox Code Playgroud)
另一方面,您也可以使用相对导入:
if __name__ == '__main__' and if __package__ is not None:
import ..api.api
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您必须使用“-m”参数启动脚本(请注意,在这种情况下,您不能提供“.py”扩展名):
python -m packageName.examples.example_one
python -m packageName.tests.test_one
Run Code Online (Sandbox Code Playgroud)
当然,您可以混合使用这两种方法,以便您的脚本无论如何调用都能正常工作:
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
Run Code Online (Sandbox Code Playgroud)