Python 3中的相对导入

我想从同一目录中的另一个文件导入一个函数。

通常,以下作品之一:

from .mymodule import myfunction
from mymodule import myfunction

…但另一个给了我其中一个错误:

ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'mymodule'
SystemError: Parent module '' not loaded, cannot perform relative import

这是为什么?

1355378 次浏览

不幸的是,这个模块需要在包内,它也需要像脚本一样运行,有时。你知道我怎么能能做到吗?

像这样的布局很常见…

main.pymypackage/__init__.pymymodule.pymyothermodule.py

…像这样的mymodule.py

#!/usr/bin/env python3
# Exported functiondef as_int(a):return int(a)
# Test function for moduledef _test():assert as_int('1') == 1
if __name__ == '__main__':_test()

…像这样的myothermodule.py

#!/usr/bin/env python3
from .mymodule import as_int
# Exported functiondef add(a, b):return as_int(a) + as_int(b)
# Test function for moduledef _test():assert add('1', '1') == 2
if __name__ == '__main__':_test()

…像这样的main.py

#!/usr/bin/env python3
from mypackage.myothermodule import add
def main():print(add('1', '1'))
if __name__ == '__main__':main()

…当您运行main.pymypackage/mymodule.py时,它可以正常工作,但由于相对导入…

from .mymodule import as_int

你应该用的方法是…

python3 -m mypackage.myothermodule

…但它有点冗长,并且不能很好地与#!/usr/bin/env python3这样的sheband行混合。

对于这种情况,假设名称mymodule是全局唯一的,最简单的解决方法是避免使用相对导入,而只使用…

from mymodule import as_int

…但是,如果它不是唯一的,或者您的包结构更复杂,您需要在PYTHONPATH中包含包含您的包目录的目录,并像这样做…

from mypackage.mymodule import as_int

…或者如果你想让它“开箱即用”,你可以先从代码中的PYTHONPATH开始…

import sysimport os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))sys.path.append(os.path.dirname(SCRIPT_DIR))
from mypackage.mymodule import as_int

这是一种痛苦,但有一个线索,为什么在一封电子邮件中由某个Guido van Rossum编写…

我是-1在这个和任何其他提议的旋转__main__机器。唯一的用例似乎是运行发生的脚本生活在模块的目录中,我一直认为这是一个反模式要让我改变主意你得说服我不是的

在包内运行脚本是否是反模式是主观的,但我个人发现它在包含一些自定义wxPython小部件的包中非常有用,因此我可以为任何源文件运行脚本以显示仅包含该小部件的wx.Frame用于测试目的。

我遇到了这个问题。一个破解的解决方法是通过一个if/else块导入,如下所示:

#!/usr/bin/env python3#myothermodule
if __name__ == '__main__':from mymodule import as_intelse:from .mymodule import as_int

# Exported functiondef add(a, b):return as_int(a) + as_int(b)
# Test function for moduledef _test():assert add('1', '1') == 2
if __name__ == '__main__':_test()

补充说明

PEP 328

相对导入使用模块的__name__属性来确定模块在包层次结构中的位置。如果模块的名称不不包含任何包信息(例如设置为“__main__”)然后相对导入被解析,就好像模块是顶级一样模块,无论模块实际位于文件的何处系统。

在某些时候PEP 338PEP 328冲突:

…相对导入依赖于__name__来确定当前模块在包层次结构中的位置。在主模块中,__name__的值总是'__main__',所以显式的相对导入将始终失败(因为它们仅适用于包内的模块)

为了解决这个问题,PEP 366引入了顶级变量#0

通过添加一个新的模块级属性,这个PEP允许相对如果使用-m执行模块,则导入自动工作模块本身中的少量样板将允许当文件按名称执行时,相对导入将起作用。[…]当它[属性]存在时,相对导入将基于此属性而不是模块__name__属性。[…]当主模块由其文件名指定时,__package__属性将设置为。[…]当导入系统在模块没有__package__设置(或设置为无),它将计算并存储正确的值__name__. r分区('.')[ 0]对于普通模块__name__包初始化模块)

(强调我的)

如果__name__'__main__'__name__.rpartition('.')[0]返回空字符串。这就是为什么错误描述中有空字符串文字:

SystemError: Parent module '' not loaded, cannot perform relative import

CPython#0函数的相关部分:

if (PyDict_GetItem(interp->modules, package) == NULL) {PyErr_Format(PyExc_SystemError,"Parent module %R not loaded, cannot perform relative ""import", package);goto error;}

如果CPython在interp->modules(可访问为#2)中找不到package(包的名称),则会引发此异常。由于sys.modules"将模块名称映射到已加载的模块的字典",现在很明显父模块必须在执行相对导入之前显式绝对导入

备注:问题18018的补丁添加了另一个#0块,它将执行之前上面的代码:

if (PyUnicode_CompareWithASCIIString(package, "") == 0) {PyErr_SetString(PyExc_ImportError,"attempted relative import with no known parent package");goto error;} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {...*/

如果package(同上)为空字符串,则错误消息将为

ImportError: attempted relative import with no known parent package

但是,您只能在Python 3.6或更高版本中看到这一点。

解决方案#1:使用-m运行脚本

考虑一个目录(这是一个Python):

.├── package│   ├── __init__.py│   ├── module.py│   └── standalone.py

中的所有文件都以相同的2行代码开头:

from pathlib import Pathprint('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())

我包含这两行只有是为了使操作的顺序显而易见。我们可以完全忽略它们,因为它们不影响执行。

__init__. pymodule.py仅包含这两行(即,它们实际上是空的)。

standalone.py还尝试通过相对导入导入module.py

from . import module  # explicit relative import

我们很清楚/path/to/python/interpreter package/standalone.py会失败。但是,我们可以使用#1命令行选项运行"搜索#2以查找命名模块并将其内容作为#3模块执行"的模块:

vaultah@base:~$ python3 -i -m package.standaloneImporting /home/vaultah/package/__init__.pyRunning /home/vaultah/package/standalone.pyImporting /home/vaultah/package/module.py>>> __file__'/home/vaultah/package/standalone.py'>>> __package__'package'>>> # The __package__ has been correctly set and module.py has been imported.... # What's inside sys.modules?... import sys>>> sys.modules['__main__']<module 'package.standalone' from '/home/vaultah/package/standalone.py'>>>> sys.modules['package.module']<module 'package.module' from '/home/vaultah/package/module.py'>>>> sys.modules['package']<module 'package' from '/home/vaultah/package/__init__.py'>

-m为您完成所有导入工作并自动设置__package__,但您可以在

解决方案#2:手动设置__package__

请将其视为概念验证而不是实际解决方案。它不太适合在现实世界的代码中使用。

PEP 366有一个解决这个问题的方法,但是,它是不完整的,因为仅设置__package__是不够的。您需要在模块层次结构中至少导入N个前面的包,其中N是将搜索要导入模块的父目录(相对于脚本目录)的数量。

因此,

  1. 将当前模块的第N个前身的父目录添加到sys.path

  2. sys.path中删除当前文件的目录

  3. 使用其完全限定名称导入当前模块的父模块

  4. __package__设置为来自2的完全限定名称

  5. 执行相对导入

我将从解决方案#1借用文件并添加更多子包:

package├── __init__.py├── module.py└── subpackage├── __init__.py└── subsubpackage├── __init__.py└── standalone.py

这次standalone.py将使用以下相对导入从包中导入module.py

from ... import module  # N = 3

我们需要在这一行前面加上样板代码,使其工作。

import sysfrom pathlib import Path
if __name__ == '__main__' and __package__ is None:file = Path(__file__).resolve()parent, top = file.parent, file.parents[3]
sys.path.append(str(top))try:sys.path.remove(str(parent))except ValueError: # Already removedpass
import package.subpackage.subsubpackage__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3

它允许我们通过文件名执行standalone.py

vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.pyRunning /home/vaultah/package/subpackage/subsubpackage/standalone.pyImporting /home/vaultah/package/__init__.pyImporting /home/vaultah/package/subpackage/__init__.pyImporting /home/vaultah/package/subpackage/subsubpackage/__init__.pyImporting /home/vaultah/package/module.py

可以找到包装在函数中的更通用的解决方案这里。示例用法:

if __name__ == '__main__' and __package__ is None:import_parents(level=3) # N = 3
from ... import modulefrom ...module.submodule import thing

解决方案#3:使用绝对导入和setup工具

这些步骤是-

  1. 将显式相对进口替换为等效绝对进口

  2. 安装package以使其可导入

例如,目录结构可能如下

.├── project│   ├── package│   │   ├── __init__.py│   │   ├── module.py│   │   └── standalone.py│   └── setup.py

setup.py在哪里

from setuptools import setup, find_packagessetup(name = 'your_package_name',packages = find_packages(),)

其余的文件是从解决方案#1借来的。

安装将允许您导入包,而不管您的工作目录如何(假设没有命名问题)。

我们可以修改standalone.py来使用这个优势(步骤1):

from package import module  # absolute import

将您的工作目录更改为project并运行/path/to/python/interpreter setup.py install --user--user您的站点包目录中安装包)(步骤2):

vaultah@base:~$ cd projectvaultah@base:~/project$ python3 setup.py install --user

让我们验证现在可以将standalone.py作为脚本运行:

vaultah@base:~/project$ python3 -i package/standalone.pyRunning /home/vaultah/project/package/standalone.pyImporting /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.pyImporting /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py>>> module<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>>>> import sys>>> sys.modules['package']<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>>>> sys.modules['package.module']<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>

注意:如果你决定沿着这条路走下去,你最好使用虚拟环境来隔离安装包。

解决方案#4:使用绝对导入和一些样板代码

坦率地说,安装是不必要的-您可以在脚本中添加一些样板代码以使绝对导入工作。

我要从解决方案standalone.py借用文件并更改standalone.py

  1. 的父目录添加到sys.path之前,尝试使用绝对导入从导入任何内容:

    import sysfrom pathlib import Path # if you haven't already done sofile = Path(__file__).resolve()parent, root = file.parent, file.parents[1]sys.path.append(str(root))
    # Additionally remove the current file's directory from sys.pathtry:sys.path.remove(str(parent))except ValueError: # Already removedpass
  2. Replace the relative import by the absolute import:

    from package import module  # absolute import

standalone.py runs without problems:

vaultah@base:~$ python3 -i package/standalone.pyRunning /home/vaultah/package/standalone.pyImporting /home/vaultah/package/__init__.pyImporting /home/vaultah/package/module.py>>> module<module 'package.module' from '/home/vaultah/package/module.py'>>>> import sys>>> sys.modules['package']<module 'package' from '/home/vaultah/package/__init__.py'>>>> sys.modules['package.module']<module 'package.module' from '/home/vaultah/package/module.py'>

我觉得我应该警告你:如果你的项目结构复杂,尽量不要这样做。


作为附注,PEP 8建议使用绝对导入,但指出在某些情况下可以接受明确的相对导入:

建议绝对导入,因为它们通常更具可读性并倾向于表现更好(或至少给出更好的错误然而,明确的相对进口是可以接受的绝对进口的替代品,特别是在处理复杂包布局,其中使用绝对导入将是不必要的冗长。

如果两个包都在您的导入路径(sys.path)中,并且您想要的模块/类在example/example.py中,那么要访问该类而不进行相对导入尝试:

from example.example import fkt

为了避免这个问题,我用重新包装包设计了一个解决方案,它已经为我工作了一段时间。它将上层目录添加到lib路径:

import repackagerepackage.up()from mypackage.mymodule import myfunction

Repackage可以使用智能策略(检查调用堆栈)进行适用于各种情况的相对导入。

把这个放在包的__init__. py文件中

# For relative imports to work in Python 3.6import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

假设你的包是这样的:

├── project│   ├── package│   │   ├── __init__.py│   │   ├── module1.py│   │   └── module2.py│   └── setup.py

现在在您的包中使用常规导入,例如:

# in module2.pyfrom module1 import class1

这适用于python 2和3。

希望这对有人有价值-我看了六个stackoverflow帖子,试图找出类似于上面发布的内容的相对导入。我按照建议设置了一切,但我仍然击中了ModuleNotFoundError: No module named 'my_module_name'

由于我只是在本地开发和玩,我没有创建/运行setup.py文件。我显然也没有设置我的PYTHONPATH

我意识到,当我像测试与模块位于同一目录中一样运行代码时,我找不到我的模块:

$ python3 test/my_module/module_test.py                                                                                                               2.4.0Traceback (most recent call last):File "test/my_module/module_test.py", line 6, in <module>from my_module.module import *ModuleNotFoundError: No module named 'my_module'

但是,当我明确指定路径时,事情开始工作:

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0...........----------------------------------------------------------------------Ran 11 tests in 0.001s
OK

因此,如果有人尝试了一些建议,相信他们的代码结构正确,并且仍然发现自己处于与我类似的情况,如果您不将当前目录导出到PYTHONPATH,请尝试以下任何一种:

  1. 运行你的代码并显式包含路径,如下所示:$ PYTHONPATH=. python3 test/my_module/module_test.py
  2. 要避免调用PYTHONPATH=.,请创建一个setup.py文件,其中包含以下内容并运行python setup.py development将包添加到路径:
# setup.pyfrom setuptools import setup, find_packages
setup(name='sample',packages=find_packages())

我需要从主项目目录运行python3才能使其工作。

例如,如果项目具有以下结构:

project_demo/├── main.py├── some_package/│   ├── __init__.py│   └── project_configs.py└── test/└── test_project_configs.py

解决方案

我会在文件夹project_demo/中运行python3,然后执行一个

from some_package import project_configs

我认为最好的解决方案是为您的模块创建一个包:这里是关于如何做到这一点的更多信息。

一旦你有了一个包,你不需要担心相对导入,你可以只做绝对导入。

我遇到了类似的问题:我需要一个Linux服务和cgi插件,它们使用公共常量进行协作。这样做的“自然”方法是将它们放在包的init.py中,但我无法使用-m参数启动cgi插件。

我的最终解决方案类似于上面的解决方案#2:

import sysimport pathlib as pimport importlib
pp = p.Path(sys.argv[0])pack = pp.resolve().parent
pkg = importlib.import_module('__init__', package=str(pack))

缺点是必须用pkg作为常量(或常用函数)的前缀:

print(pkg.Glob)

我得到了这个尝试相对导入没有已知的父包

在我的程序中,我使用当前路径中的文件来导入其函数。

from .filename import function

然后我用包名修改了当前路径(圆点)。这解决了我的问题。

from package_name.filename import function

希望以上答案对你有所帮助。

对于PyCharm用户:

我也得到了ImportError: attempted relative import with no known parent package,因为我添加了.符号来消除PyCharm解析错误。PyCharm不准确地报告无法找到:

lib.thing import function

如果您将其更改为:

.lib.thing import function

它沉默了错误,但随后你得到了前面提到的ImportError: attempted relative import with no known parent package。忽略PyCharm的解析器。它是错误的,代码运行良好,不管它说什么。

将导入的文件移动到外部目录会有所帮助。
当您的主文件在其自己的目录中创建任何其他文件时,这特别有用。
例如:
之前:

Project|---dir1|-------main.py|-------module1.py

之后:

Project|---module1.py|---dir1|-------main.py

如果上述方法都不适用于您,您可以显式指定模块。

目录:

├── Project│     ├── Dir│     │    ├── __init__.py│     │    ├── module.py│     │    └── standalone.py

解决方案:

#in standalone.pyfrom Project.Dir.module import ...

模块-要导入的模块

TLDR;通过在python脚本的切入点中添加以下内容,将脚本路径附加到系统路径

import os.pathimport sysPACKAGE_PARENT = '..'SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))

就是这样,现在您可以在PyCharma以及从终端运行您的项目!

我尝试了上述所有方法,但都无济于事,只是意识到我错误地在包名中使用了-

简而言之,在__init__.py所在的目录中不要有-。在发现如此愚蠢之后,我从未感到高兴。

我有一个类似的问题,并通过在工作目录中为包创建符号链接来解决它:

ln -s ../../../my_package my_package

然后像往常一样导入它:

import my_package

我知道这更像是一个“Linux”的解决方案,而不是“Python”的解决方案。

我的样板在package可运行的独立中使用相对导入来制作module

package/module.py

## Standalone boilerplate before relative importsif __package__ is None:DIR = Path(__file__).resolve().parentsys.path.insert(0, str(DIR.parent))__package__ = DIR.name
from . import variable_in__init__pyfrom . import other_module_in_package...

现在您可以以任何方式使用您的模块:

  1. 照常运行模块:python -m package.module
  2. 将其用作模块:python -c 'from package import module'
  3. 独立运行:python package/module.py
  4. 或者使用sheband(#!/bin/env python)只是:package/module.py

NB!如果你的module与你的package同名,使用sys.path.append而不是sys.path.insert会给你一个难以跟踪的错误。例如my_script/my_script.py

当然,如果您从包层次结构中的较高级别进行相对导入,那么这还不够,但对于大多数情况下,这还可以。

当我使用Django时,我经常遇到这种情况,因为很多功能都是从manage.py脚本执行的,但我也希望我的一些模块可以直接作为脚本运行(理想情况下,你会让它们成为manage.py指令,但我们还没有)。

这是这样一个项目可能是什么样子的模型;

├── dj_app│   ├── models.py│   ├── ops│   │   ├── bar.py│   │   └── foo.py│   ├── script.py│   ├── tests.py│   ├── utils.py│   └── views.py└── manage.py

这里的重要部分是manage.pydj_app/script.pydj_app/tests.py。我们还有子模块dj_app/ops/bar.pydj_app/ops/foo.py,其中包含我们希望在整个项目中使用的更多项目。

问题的根源通常来自于希望您的dj_app/script.py脚本方法在dj_app/tests.py中具有测试用例,这些用例在您运行manage.py test时被调用。

这就是我如何设置项目及其import

# dj_app/ops/foo.py# Foo operation methods and classesfoo_val = "foo123"

.

# dj_app/ops/bar.py# Bar operations methods and classesbar_val = "bar123"

.

# dj_app/script.py# script to run app methods from CLI
# if run directly from command lineif __name__ == '__main__':from ops.bar import bar_valfrom ops.foo import foo_val
# otherwiseelse:from .ops.bar import bar_valfrom .ops.foo import foo_val
def script_method1():print("this is script_method1")print("bar_val: {}".format(bar_val))print("foo_val: {}".format(foo_val))

if __name__ == '__main__':print("running from the script")script_method1()

.

# dj_app/tests.py# test cases for the app# do not run this directly from CLI or the imports will breakfrom .script import script_method1from .ops.bar import bar_valfrom .ops.foo import foo_val
def main():print("Running the test case")print("testing script method")script_method1()
if __name__ == '__main__':print("running tests from command line")main()

.

# manage.py# just run the test cases for this exampleimport dj_app.testsdj_app.tests.main()

.

manage.py运行测试用例;

$ python3 manage.pyRunning the test casetesting script methodthis is script_method1bar_val: bar123foo_val: foo123

自己运行脚本;

$ python3 dj_app/script.pyrunning from the scriptthis is script_method1bar_val: bar123foo_val: foo123

请注意,如果您尝试直接运行test.py,则会出现错误,因此不要这样做;

$ python3 dj_app/tests.pyTraceback (most recent call last):File "dj_app/tests.py", line 5, in <module>from .script import script_method1ModuleNotFoundError: No module named '__main__.script'; '__main__' is not a package

如果我遇到更复杂的导入情况,我通常最终会实现这样的东西来破解它;

import osimport sysTHIS_DIR = os.path.dirname(os.path.realpath(__file__))sys.path.insert(0, THIS_DIR)from script import script_method1sys.path.pop(0)

这是我的项目结构

├── folder|   |│   ├── moduleA.py|   |   ||   |   └--function1()|   |       └~~ uses function2()|   |│   └── moduleB.py|       ||       └--function2()|└── main.py└~~ uses function1()

这里我的moduleA导入moduleBmain导入moduleA

我在moduleA中添加了下面的片段来导入moduleB

try:from .moduleB import function2except:from moduleB import function2

现在我可以分别执行main.pymoduleA.py

这是解决办法吗?

太长别读:@Aya的答案,更新了pathlib库,并为未定义__file__的Jupyter笔记本工作:

您想导入../my_Folder_where_the_package_lives/my_package.py下定义的my_function你在哪里写代码?

然后这样做:

import osimport sysimport pathlib
PACKAGE_PARENT = pathlib.Path(__file__).parent#PACKAGE_PARENT = pathlib.Path.cwd().parent # if on jupyter notebookSCRIPT_DIR = PACKAGE_PARENT / "my_Folder_where_the_package_lives"sys.path.append(str(SCRIPT_DIR))
from my_package import my_function

系统错误:父模块“未加载,无法执行相对导入

这意味着您正在将包内的模块作为脚本运行。在包内混合脚本是棘手,应该尽可能避免。使用导入包并运行scripty函数的包装器脚本。

如果您的顶级目录名为foo,它位于您的PYTHONPATH模块搜索路径上,并且您在那里有一个包bar(您希望它是一个__init__.py文件的目录),脚本应该是不要放在里面#2,但应该存在于foo充其量中。

请注意,脚本模块的不同之处在于,它们被用作python命令的文件名参数,通过使用python <filename>或通过#!(sheang)行。它将直接加载为#3模块(这就是为什么if __name__ == "__main__":在脚本中工作),并且没有包上下文可用于相对导入。

你的选择

  • 如果可以,请使用setuptools(或poetryflit,这有助于简化打包)打包您的项目,并创建控制台脚本入口点;使用pip安装您的项目,然后创建知道如何正确导入包的脚本。您可以使用pip install -e .在本地安装包,因此它仍然可以在原地编辑。

  • 否则,永远不要使用python path/to/packagename/file.py,总是使用python path/to/script.pyscript.py可以使用from packagename import ...

  • 作为后备,您可以使用#0命令行开关告诉Python导入一个模块并将其用作__main__文件。这不适用于sheang行,因为不再有脚本文件了。

    如果您使用python -m foo.bar并且在sys.path目录中找到foo/bar.py,则使用正确的包上下文将其作为__main__导入并执行。如果bar也是一个包,在foo/中,它必须有一个__main__.py文件(因此foo/bar/__main__.py作为sys.path目录的路径)。

  • 在极端情况下,通过直接设置__package__添加Python用于解析相对导入的元数据;文件foo/bar/spam.py,可导入为foo.bar.spam,被赋予全局__package__ = "foo.bar"。它只是另一个全局,就像__file____name__一样,由Python在导入时设置。

sys.path

以上所有要求您的包可以导入,这意味着它需要在#0中列出的目录(或zipfile)之一中找到。这里也有几个选项:

  • 找到path/to/script.py的目录(因此path/to)会自动添加到sys.path。执行python path/to/foo.py会将path/to添加到sys.path

  • 如果您打包了您的项目(使用setuptoolspoetryflit或其他Python打包工具)并安装了它,则该包已经添加到正确的位置。

  • 作为最后的手段,自己将正确的目录添加到sys.path。如果包可以相对于脚本文件定位,请使用脚本全局命名空间中的__file__变量(例如,使用#2对象HERE = Path(__file__).resolve().parent是对文件所在目录的引用,作为绝对路径)。

从同一目录导入

首先,您可以从同一目录导入。

这是文件结构…

Folder|├─ Scripts|   ├─ module123.py|├─ main.py├─ script123.py

这是main.py

from . import script123from Scripts import module123

如您所见,从.导入从当前目录导入。

注意:如果使用IDLE以外的任何东西运行,请确保您的终端在运行前导航到与main.py文件相同的目录。

此外,从本地文件夹导入也有效。

从父目录导入

我的github gist在这里所示,有以下方法。

取以下文件树…

ParentDirectory├─ Folder|   ||   ├─ Scripts|   |   ├─ module123.py|   ||   ├─ main.py|   ├─ script123.py|├─ parentModule.py

然后,只需将此代码添加到main.py文件的顶部。

import inspectimport osimport sys
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))parent_dir = os.path.dirname(current_dir)sys.path.insert(0, parent_dir)
from ParentDirectory import Stuff

以下解决方案在python3上进行了测试

├── classes|   ||   ├──__init__.py|   |│   ├── userclass.py|   |   ||   |   └--viewDetails()|   ||   |│   └── groupclass.py|       ||       └--viewGroupDetails()|└── start.py└~~ uses function1()

现在,为了使用userclass或group pclass的viewGroup详细信息,首先在_init中定义_class ess目录的. py。

例如:在_init_. py

from .userclasss import viewDetails
from .groupclass import viewGroupDetails

Step2:现在,在start.py我们可以直接导入view详细信息

上一篇:start.py

from classes import viewDetailsfrom classes import viewGroupDetails

我为Python创建了一个新的实验性导入库:超进口

它为程序员提供了对导入的更多控制并使它们明确无误。当导入失败时,它还提供了更好的错误消息。

它允许您进行相对的、基于文件系统的导入,这些导入始终有效,无论您如何运行代码,也无论您当前的工作目录是什么。运行脚本或模块并不重要。您也不必更改可能会产生其他副作用的sys.path。

你就会改变

from .mymodule import myfunction

import ultraimportmyfunction = ultraimport('__dir__/mymodule.py', 'myfunction')

这样,导入将始终工作,即使您将代码作为脚本运行。

像这样导入脚本的一个问题是后续的相对导入可能会失败。超导入有一个内置的预处理器来自动重写相对导入。

在尝试编写可以作为模块或可执行脚本加载的python文件时,我遇到了类似的问题。

设置

/path/to/project/├── __init__.py└── main.py└── mylib/├── list_util.py└── args_util.py

有:

main.py:

#!/usr/bin/env python3import sysimport mylib.args_util
if __name__ == '__main__':print(f'{mylib.args_util.parseargs(sys.argv[1:])=}')

mylib/list_util.py:

def to_int_list(args):return [int(x) for x in args]

mylib/args_util.py:

#!/usr/bin/env python3import sysfrom . import list_util as lu
def parseargs(args):return sum(lu.to_int_list(args))
if __name__ == '__main__':print(f'{parseargs(sys.argv[1:])=}')

产出

$ ./main.py 1 2 3mylib.args_util.parseargs(sys.argv[1:])=6
$ mylib/args_util.py 1 2 3Traceback (most recent call last):File "/path/to/project/mylib/args_util.py", line 10, in <module>from . import list_util as luImportError: attempted relative import with no known parent package

解决方案

我选择了Bash/Python多语言解决方案。该程序的Bash版本只需调用python3 -m mylib.args_util然后退出。

Python版本忽略了Bash代码,因为它包含在docstring中。

Bash版本忽略了Python代码,因为它使用exec来停止解析/运行行。

mylib/args_util.py:

#!/bin/bash# -*- Mode: python -*-''''trueexec /usr/bin/env python3 -m mylib.args_util "$@"'''
import sysfrom . import list_util as lu
def parseargs(args):return sum(lu.to_int_list(args))
if __name__ == '__main__':print(f'{parseargs(sys.argv[1:])=}')

产出

$ ./main.py 1 2 3mylib.args_util.parseargs(sys.argv[1:])=6
$ mylib/args_util.py 1 2 3parseargs(sys.argv[1:])=6

补充说明

  • 第1行:#!/bin/bash;这是“sheband”行;它告诉交互式shell如何运行此脚本。
    • Python:忽略(注释)
    • Bash:忽略(评论)
  • 第2行:# -*- Mode: python -*-可选;这被称为“mode-line”;它告诉Emacs在读取文件时使用Python语法高亮显示,而不是猜测语言是Bash。
    • Python:忽略(注释)
    • Bash:忽略(评论)
  • 第3行:''''true
    • Python:将其视为以'true\n开头的未分配文档字符串
    • Bash:将其视为三个字符串(其中前两个为空字符串),扩展到true(即'' + '' + 'true' = 'true');然后运行true(什么都不做)并继续下一行
  • 第4行:exec /usr/bin/env python3 -m mylib.args_util "$@"
    • Python:仍然将其视为第3行文档字符串的一部分。
    • Bash:运行python3 -m mylib.args_util然后退出(它不会解析超出此行的任何内容)
  • 第5行:'''
    • Python:将其视为第3行文档字符串的结尾。
    • Bash:不解析此行

警告

  • 这在Windows上不起作用:
    • 解决方法:使用WSL或Batch包装脚本调用python -m mylib.args_util
  • 仅当当前工作目录设置为/path/to/project/时才有效。
    • 解决方法:调用/usr/bin/env时设置PYTHONPATH
      #!/bin/bash# -*- Mode: python -*-''''trueexec /usr/bin/env python3 \PYTHONPATH="$(cd "$(dirname "$0")/.." ; pwd)" \-m mylib.args_util "$@"'''

这里有三条线给那些不同意圭多的人:

import sysfrom pathlib import Pathsys.path.append(str(Path(sys.argv[0]).absolute().parent.parent))

希望有帮助。

太长别读

您只能相对导入模块在同一包中的另一个模块中。

概念澄清

我们在书/文档/文章中看到很多示例代码,它们向我们展示了如何相对导入模块,但是当我们这样做时,它失败了。

原因是,用一句简单的话来说,我们没有像python模块机制期望的那样运行代码,即使代码写得完全正确。这就像某种运行时的东西。

模块加载取决于您运行代码的方式。这就是混乱的根源。

什么是模块?

当且仅当模块被另一个文件导入时,它才是python文件。给定一个文件mod.py,它是一个模块吗?是和否,如果你运行python mod.py,它不是一个模块,因为它没有被导入。

什么是包裹?

包是包含Python模块的文件夹。

顺便说一句,如果您不需要任何包初始化或自动加载子模块,Python 3.3中不需要__init__.py。您不需要在目录中放置空白__init__.py

这证明了一个包只是一个文件夹,只要有文件被导入。


真实答案

现在,这种描述变得更加清晰。

您只能相对导入模块在同一包中的另一个模块中。

给定一个目录:

. CWD|-- happy_maker.py # content: print('Sends Happy')`-- me.py # content: from . import happy_maker

运行python me.py,我们得到attempted relative import with no known parent package

me.py是直接运行的,它不是一个模块,我们不能在其中使用相对导入。

解决方案1

使用import happy_maker而不是from . import happy_maker

解决方案2

将我们的工作目录切换到父文件夹。

. CWD|-- happy|   |-- happy_maker.py`-- me.py

运行python -m happy.me

当我们在包含happy的目录中时,happy是一个包,me.py, happy_maker.py是模块,我们现在可以使用相对导入,我们仍然想运行me.py,所以我们使用-m,这意味着将模块作为脚本运行。


PythonIdiom

. CWD|-- happy|   |-- happy_maker.py # content: print('Sends Happy')|   `-- me.py # content: from . import happy_maker`-- main.py # content: import happy.me

这个结构是python习语。main是我们的脚本,Python的最佳实践。最后,我们到了那里。


兄弟姐妹或祖父母

另一个共同需求:

.|-- happy|   |-- happy_maker.py|   `-- me.py`-- sad`-- sad_maker.py

我们想在me.py中导入sad_maker,如何做到这一点?

首先,我们需要在同一个包中制作happysad,所以我们必须上一个目录级别。然后me.py中的from ..sad import sad_maker

仅此而已。

我也犯了同样的错误,我的项目结构就像

->project->vendors->vendors.py->main.py

我试着像这样打电话

from .vendors.Amazon import Amazom_Purchase

这里它抛出了一个错误,所以我简单地通过从语句中删除第一个.来修复它

from vendors.Amazon import Amazom_Purchase

希望这有帮助。

值得注意的是,有时缓存会导致所有这些-我在将类重新排列到新目录后尝试了不同的事情,并且在删除__pycache__后相对导入开始工作

如果导入以下内容:

from . import something

这对您不起作用,因为这是python包导入,不适用于您的常规实现,这里有一个示例来展示如何使用它:

文件夹结构:

.└── funniest├── funniest│   ├── __init__.py│   └── text.py├── main.py└── setup.py

内部__init__.py添加:

def available_module():return "hello world"

text.py添加:

from . import available_module

里面setup.py添加

from setuptools import setup
setup(name='funniest',version='0.1',description='The funniest joke in the world',url='http://github.com/storborg/funniest',author='Flying Circus',author_email='flyingcircus@example.com',license='MIT',packages=['funniest'],zip_safe=False)

现在,这是最重要的部分您需要安装软件包:

pip install .

在我们系统中使用相同Python的其他任何地方,我们现在都可以这样做:

>> import funnies.text as fun>> fun.available_module()

这应该输出'hello world'

您可以在main.py中测试它(这不需要安装任何包)

这里也是main.py

import funniest.text as funprint(fun.available_module())