我已经尝试通读有关同级导入甚至包文档的问题,但是我还没有找到答案。

具有以下结构:

├── 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模块导入testsapi目录中的脚本并从命令行运行?

此外,我想以避免每个文件的丑陋sys.path.insert hack。当然可以
用Python完成,对吧?

评论

我建议跳过所有sys.path技巧,并阅读迄今为止(7年后!)发布的唯一实际解决方案。

顺便说一下,还有另一个好的解决方案的空间:将可执行代码与库代码分开;大多数情况下,程序包中的脚本不应以可执行文件开头。

这对问题和答案都非常有帮助。我只是很好奇,在这种情况下,“接受的答案”与获得赏金的人为什么不一样?

@ Aran-Fey在这些相对导入错误问答中,这被低估了。我一直以来都在寻找黑客手段,但是从深层次上,我知道有一种简单的方法可以解决问题。并不是说这是这里阅读的每个人的解决方案,但这是对很多人的一个很好的提醒。

#1 楼

自从我在下面写下答案以来,已经有7年了。修改sys.path仍然是一种快捷技巧,对私有脚本来说效果很好,但是有了一些改进



(无论是否在virtualenv中)安装软件包都可以为您提供所需的信息,尽管我建议使用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的父文件夹中)。

评论


您不必使用-m格式从项目目录运行,也可以安装软件包(pip和virtualenv使其变得容易)

– jfs
2014年5月8日13:15

pytest如何为您找到api包?有趣的是,我发现了这个线程,因为我在使用pytest和同级包导入时遇到了这个问题。

– JuniorIncanter
19年1月18日,0:30

请问两个问题。 1.对我来说,您的模式似乎可以不使用__package__ =“ examples”。为什么使用它? 2.在什么情况下__name__ ==“ __main__”但__package__不是None?

–actual_panda
19年11月6日在16:57

@actual_panda设置__packages__很有帮助,如果您想让example.api之类的绝对路径运行iirc(但是自从我上次这样做以来已经很长时间了),并且检查该包是否为None,对于怪异的情况和对未来的要求,在大多数情况下都是故障保护。

– Evpok
1月30日10:48

天哪,如果只有其他语言能够像在Python中一样轻松地完成相同的过程。我明白了为什么每个人都喜欢这种语言。顺便说一句,文档也很棒。我喜欢从非结构化文本中提取返回类型,这与Javadoc和phpdoc相比是一个不错的变化。 ffs ....

–马特
7月8日16:04



#2 楼


对sys.path hack感到厌倦了吗?
有大量的sys.path.append -hacks可用,但是我找到了解决问题的另一种方法。
摘要

将代码包装到一个文件夹(例如packaged_stuff
在使用setuptools.setup()的地方使用create setup.py脚本。
使用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:\tmp\test_imports\
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", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

/>
步骤

将setup.py文件添加到根目录


setup.py的内容为*
from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())


使用虚拟环境


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

创建虚拟环境

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]


pip以可编辑状态安装项目


使用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


myproject.添加到您的导入中


请注意,您仅需将myproject.添加到否则无法正常工作的导入中。在没有setup.pypip 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文档。
**实际上,您可以将虚拟环境放在任何地方都很难磁盘。

评论


感谢您的详细帖子。这是我的问题。如果我按照您说的去做,并且冻结了一个点,我会得到一行-e git + https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myproject关于如何解决的任何想法吗?

– Si Mon
18/12/11在16:42

相对导入解决方案为什么不起作用?我相信您,但是我想了解Python的复杂系统。

–贾里德·尼尔森(Jared Nielsen)
19年2月1日,下午2:56

有没有人对ModuleNotFoundError有问题?按照以下步骤,我已经将'myproject'安装到virtualenv中,当我进入一个解释会话并运行import myproject时,我得到ModuleNotFoundError:没有名为'myproject'的模块?点列表已安装| grep myproject显示它在那里,目录正确,并且pip和python版本均正确无误。

–这些种类
19年8月28日在14:40



花了大约2个小时试图弄清楚如何使相对进口商品起作用,而这个答案才是最终实际上做了明智的选择。 👍👍

– Graham Lea
19年11月28日在4:29

我认为令人作呕的是,我必须进入stackoverflow才能找到有关如何正确执行相对导入的真正答案。有关此内容的文档确实需要改进。

–teuber789
2月20日17:21



#3 楼

这是我在tests文件夹中的Python文件顶部插入的另一种选择:

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


评论


+1确实很简单,而且效果很好。您需要将父类添加到导入中(例如api.api,examples.example_two),但我更喜欢这样做。

–伊文·普莱斯
2012年6月1日21:35

我认为值得向新手(像我自己)提及..这是相对于您要从中执行的目录的,而不是包含该测试/示例文件的目录。我正在从项目目录执行,而我需要./代替。希望这对其他人有帮助。

–约书亚·德特维尔(Joshua Detwiler)
18年5月23日在18:01



@JoshDetwiler,绝对是。我没有意识到这一点。谢谢。

– doak
19年1月31日在11:12

这是一个很差的答案。破解路径不是一个好习惯;在python世界中使用了多少是可耻的。这个问题的主要要点之一就是看如何在避免这种黑客攻击的同时进行导入。

–teuber789
2月20日17:20



sys.path.insert(0,os.path.join(os.path.dirname(__ file__),'..'))@JoshuaDetwiler

– vldbnc
5月15日18:27



#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(不是Python软件包)中删除examples,并在安装了api的virtualenv中运行示例,例如,如果您具有正确的pip install -e .,则virtualenv中的api会在内部安装setup.py软件包。

评论


@Alex答案不假定测试是API测试,除非该段明确指出“如果它们是api的单元测试”。

– jfs
16年11月24日在20:30



不幸的是,您只能从根目录运行,PyCharm仍然找不到文件,因为它的功能不错

– mhstnsc
17年11月3日17:28



@mhstnsc:不正确。激活virtualenv后,您应该可以在任何地方运行python -m api.test.test_one。如果您无法将PyCharm配置为运行测试,请尝试提出一个新的Stack Overflow问题(如果在该主题上找不到现有问题)。

– jfs
17年3月3日在18:05

@jfs我错过了虚拟环境路径,但是除了shebang行之外,我不想使用任何东西来从任何目录运行此东西。它不是与PyCharm一起运行。具有PyCharm的开发人员也知道他们已经完成并跳过了我无法使其与任何解决方案一起使用的功能。

– mhstnsc
17年11月3日在20:48



@mhstnsc在许多情况下,适当的shebang就足够了(将其指向virtualenv python二进制文件。任何不错的Python IDE都应支持virtualenv。

– jfs
18-09-25在19:43

#5 楼

我还没有掌握Python知识的必要知识,以了解在没有同级/相对导入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


评论


这仍然会为您提供api父目录,并且您不需要“ / ..”串联sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(file)))) )

– Camilo Sanchez
2014年7月23日在3:46



#6 楼

对于同级包导入,可以使用[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


评论


我使用的Click框架没有__file__全局变量,因此必须使用以下内容:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv [ 0]))))但现在可以在任何目录中使用

– GammaGames
19 Mar 8 '19在15:39



#7 楼

TLDR

此方法不需要setuptools,path hacks,其他命令行参数或在项目的每个文件中指定软件包的顶层。

您要调用的__main__的父目录中的脚本,然后从那里运行所有内容。有关进一步的解释,请继续阅读。

解释

可以完成此操作,而无需一起寻找新路径,额外的命令行参数或向每个程序添加代码以识别其含义。同胞。

之所以失败,是因为我相信之前已经提到过,被调用的程序将其__name__设置为__main__。发生这种情况时,被调用的脚本会接受自身位于程序包的顶层,并拒绝识别兄弟目录中的脚本。

但是,目录顶层下的所有内容仍然可以识别任何其他内容在顶层之下。这意味着要使同级目录中的文件相互识别/利用,您只需要做的就是从其父目录中的脚本中对其进行调用。

概念证明
dir具有以下结构:

.
|__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调用了sib2/callsib.py,调用sib2/callsib.py也会导致按照sib1/call.py的定义打印“ Got Called”。但是,如果直接调用sib1/call.py(在对导入进行了适当的更改之后),它将引发异常。即使它由其父目录中的脚本调用时可以工作,但是如果它认为自己位于程序包的顶层,则它将无法工作。

评论


OP专门询问“示例和测试目录中的脚本如何从api模块导入并从命令行运行?”该答案不适用于OPs文件夹结构。

– np8
7月20日7:29

您不需要main.py文件;您可以执行python -m sib1.call而不是执行调用sib1.call的python main.py命令。

– Ersel Er
8月3日,0:23



#8 楼

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

import api.api


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

最简单的方法来支持此操作(如您所说的那样)是从顶级目录运行示例,如下所示:

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


评论


使用Python 2.7.1,我得到以下信息:$ python examples / example.py Traceback(最近一次调用):文件“ examples / example.py”,第3行,来自api.api import API ImportError:否名为api.api的模块。我也可以通过导入api.api得到相同的结果。

– zachwill
2011年6月12日18:51



更新了我的答案...您确实必须将当前目录添加到导入路径,而无需解决。

– AJ。
2011年6月12日18:58

#9 楼

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

#10 楼

我制作了一个示例项目来演示如何处理此问题,这确实是如上所述的另一个sys.path hack。 Python Sibling导入示例,该示例依赖于:

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

,只要您的工作目录位于Python项目的根目录下,这似乎非常有效。如果有人将其部署在真实的生产环境中,那么很高兴知道它在这里是否也能正常工作。

评论


仅当您从脚本的父目录运行时,此方法才有效

– Evpok
19/12/17在20:47

#11 楼

我想评论一下np8提供的解决方案,但是我的信誉不高,所以我只想提一下,您可以按照他们的建议完全创建setup.py文件,然后从项目根目录执行pipenv install --dev -e .来将其打开变成可编辑的依赖项然后您的绝对导入将起作用例如from api.api import foo,您不必弄乱系统范围的安装。
文档

#12 楼


项目

1.1用户
1.1.1 about.py
1.1.2 init.py
1.2技术
1.2.1信息。 py
1.1.2 init.py
现在,如果要访问User包中的about.py模块,请从Tech包中的info.py模块中获取cmd(在Windows中)的项目路径,即按照上述“包”示例中的
C:\ Users \ Personal \ Desktop \ Project> **。然后从该路径输入python -m Package_name.module_name
例如,对于我们必须执行的上述Package,
C:\ Users \ Personal \ Desktop \ Project> python -m Tech。 info
Imp Points

在信息模块(即python -m Tech.info.py
)之后不要使用.py扩展名,请输入此名称,其中兄弟姐妹包处于同一级别。
-m是标志,要检查它,可以从cmd python --help


键入

#13 楼

针对主要问题:
将兄弟文件夹作为模块:
从.. import siblingfolder
从兄弟文件夹中将a_file.py调用作为模块:
从..siblingfolder导入a_file
在同级文件夹中的文件内调用a_function作为模块:
从..siblingmodule.a_file导入func_name_exists_in_a_file
最简单的方法。
转到lib / site-packages文件夹。
如果存在“ easy_install.pth”文件,则对其进行编辑,然后添加具有要使其作为模块的脚本的目录。
如果不存在,则使其成为一个...并放置您要的文件夹在其中
添加...之后,python会自动将其视为类似于站点包的文件夹,并且您可以将该文件夹或子文件夹中的每个脚本作为模块进行调用。手机,并且很难设置它,以使每个人都能轻松阅读。

#14 楼

首先,应避免使用与模块本身同名的文件。可能会中断其他导入。

导入文件时,首先解释器检查当前目录,然后搜索全局目录。

examplestests内部,您可以调用:

from ..api import api


评论


我在Python 2.7.1中得到以下信息:追溯(最近一次调用):..api import api的中文件“ example_one.py”,第3行,ValueError:尝试以非包形式进行相对导入

– zachwill
2011年6月12日20:35

哦,那么您应该将__init__.py文件添加到顶层目录。否则Python无法将其视为模块

–user780363
2011年6月13日,0:03



它不会工作。问题不是父文件夹不是包,而是因为模块的__name__是__main__而不是package.module,所以Python无法看到其父包,所以。没有指向任何东西。

– Evpok
2011年6月24日10:00