Python 3 中的相对导入
- 2024-11-19 08:38:00
- admin 原创
- 9
问题描述:
我想从同一目录中的另一个文件导入一个函数。
通常,以下方法之一有效:
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
这是为什么呢?
解决方案 1:
不幸的是,这个模块需要放在包内,有时还需要作为脚本运行。有什么想法可以实现吗?
这样的布局很常见……
main.py
mypackage/
__init__.py
mymodule.py
myothermodule.py
...像mymodule.py
这样...
#!/usr/bin/env python3
# Exported function
def as_int(a):
return int(a)
# Test function for module
def _test():
assert as_int('1') == 1
if __name__ == '__main__':
_test()
...myothermodule.py
像这样...
#!/usr/bin/env python3
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _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.py
...当您运行或时工作正常mypackage/mymodule.py
,但mypackage/myothermodule.py
由于相对导入而失败......
from .mymodule import as_int
您应该运行它的方式是使用 -m 选项并提供 Python 模块系统中的路径(而不是文件系统)...
python3 -m mypackage.myothermodule
...但是它有点冗长,并且与像这样的 shebang 行混合效果不佳#!/usr/bin/env python3
。
另一种方法是避免使用相对导入,而只使用......
from mypackage.mymodule import as_int
无论哪种方式,您都需要从的父级运行mypackage
,或将该目录添加到PYTHONPATH
(任何一个都可以确保mypackage
位于 sys.path模块搜索路径中)。或者,如果您希望它“开箱即用”,您可以PYTHONPATH
先用这个来处理代码...
import sys
import 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 的人写的一封电子邮件中可以找到原因的线索...
我对此以及任何其他提议的
__main__
机器调整都持 -1 态度。唯一的用例似乎是运行恰好位于模块目录中的脚本,我一直认为这是一种反模式。要让我改变主意,你必须说服我事实并非如此。
在包内运行脚本是否是反模式是主观的,但我个人发现它在包含一些自定义 wxPython 小部件的包中非常有用,因此我可以为任何源文件运行该脚本以显示wx.Frame
仅包含该小部件以用于测试目的。
解决方案 2:
解释
来自PEP 328
相对导入使用模块的 name 属性来确定该模块在软件包层次结构中的位置。如果模块的名称不包含任何软件包信息(例如,它被设置为“__main__”),
则相对导入将被解析为该模块是顶级模块,而不管该模块在文件系统上的实际位置。
在某些时候,PEP 338与PEP 328发生冲突:
... 相对导入依赖name来确定当前模块在包层次结构中的位置。在主模块中,name的值始终为'__main__',因此显式相对导入将始终失败(因为它们仅适用于包内的模块)
为了解决这个问题,PEP 366引入了顶级变量__package__
:
通过添加新的模块级属性,此 PEP 允许在使用-m
开关执行模块时自动进行相对导入。模块本身中的少量样板将允许在按名称执行文件时相对导入起作用。[...] 当它 [属性] 存在时,相对导入将基于此属性而不是模块name属性。[...] 当主模块由其文件名指定时,package属性将设置为None。[...]当导入系统在未设置 package (或将其设置为 None )的模块中遇到显式相对导入时,它将计算并存储正确的值(对于普通模块为 __name__.rpartition('.')[0] ,对于包初始化模块为name )
(重点是我的)
如果__name__
是'__main__'
,__name__.rpartition('.')[0]
则返回空字符串。这就是为什么错误描述中会出现空字符串文字:
SystemError: Parent module '' not loaded, cannot perform relative import
PyImport_ImportModuleLevelObject
CPython函数的相关部分:
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
如果 CPython 无法在(accessible as )package
中找到 (包的名称) ,则会引发此异常。由于是“将模块名称映射到已加载模块的字典”,因此现在很明显,在执行相对导入之前必须明确绝对导入父模块。interp->modules
`sys.modules`sys.modules
注意:来自问题 18018 的补丁添加了另一个if
块,它将在上述代码之前执行
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
包中的所有文件都以相同的两行代码开头:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
我添加这两行只是为了使操作顺序显而易见。我们可以完全忽略它们,因为它们不会影响执行。
__init__.py和module.py仅包含这两行(即,它们实际上是空的)。
standalone.py还尝试通过相对导入来导入module.py:
from . import module # explicit relative import
我们很清楚这会失败。但是,我们可以使用命令行选项/path/to/python/interpreter package/standalone.py
运行模块,该选项将“搜索命名的模块并将其内容作为模块执行”:-m
sys.path
`__main__`
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /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是将要搜索要导入的模块的父目录数(相对于脚本的目录)。
因此,
将当前模块第 N 个前身的父目录添加到
sys.path
从中删除当前文件的目录
sys.path
使用其完全限定名称导入当前模块的父模块
设置为2
__package__
的完全限定名称执行相对导入
我将从解决方案 #1中借用文件并添加更多子包:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
这次standalone.py将使用以下相对导入从包package 中导入module.py
from ... import module # N = 3
我们需要在该行前面添加样板代码,以使其正常工作。
import sys
from 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 removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
它允许我们通过文件名执行standalone.py :
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
可以在此处找到包含在函数中的更通用的解决方案。示例用法:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
解决方案 #3:使用绝对导入和setuptools
步骤如下 -
用等效的绝对导入替换显式相对导入
安装
package
以使其可导入
例如,目录结构可能如下
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
其中setup.py是
from setuptools import setup, find_packages
setup(
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
(在您的 site-packages 目录--user
中安装包)(步骤 2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
让我们验证现在是否可以将standalone.py作为脚本运行:
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /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:使用绝对导入和一些样板代码
坦率地说,安装不是必需的 - 您可以在脚本中添加一些样板代码以使绝对导入工作。
我将从解决方案 #1中借用文件并更改standalone.py:
在尝试使用绝对导入从包中导入任何内容之前,将包的父目录添加到:
sys.path
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
用绝对导入替换相对导入:
from package import module # absolute import
standalone.py运行没有问题:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /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建议使用绝对导入,但指出在某些情况下显式相对导入是可以接受的:
建议使用绝对导入,因为它们通常更易读并且行为更好(或至少提供更好的错误消息)。[...] 但是,显式相对导入是绝对导入的可接受替代方案,特别是在处理复杂的包布局时,使用绝对导入会不必要地冗长。
解决方案 3:
将其放入包的 __init__.py 文件中:
# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
假设你的包裹是这样的:
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ └── module2.py
│ └── setup.py
现在在您的包中使用常规导入,例如:
# in module2.py
from module1 import class1
这在 Python 2 和 Python 3 中都有效。
解决方案 4:
我遇到了这个问题。一个解决方法是通过 if/else 块导入,如下所示:
#!/usr/bin/env python3
#myothermodule
if __name__ == '__main__':
from mymodule import as_int
else:
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __name__ == '__main__':
_test()
解决方案 5:
SystemError:父模块''未加载,无法执行相对导入
这意味着您正在以脚本的形式运行包内的模块。在包内混合使用脚本比较棘手,应尽可能避免。请使用包装器脚本来导入包并运行您的scripty
函数。
如果您的顶级目录名为foo
,它位于您的PYTHONPATH
模块搜索路径上,并且您在那里有一个包bar
(它是您期望__init__.py
文件所在的目录),则脚本不应该放在 里面,而应该bar
foo
最多存在于 中。
请注意,此处的脚本与模块不同,因为它们被用作python
命令的文件名参数,无论是使用python <filename>
还是通过(shebang) 行。它直接作为模块#!
加载(这就是在脚本中工作的原因),并且没有用于构建相对导入的包上下文。__main__
`if name == "__main__":`
您的选择
如果可以,请使用
setuptools
(或poetry
或flit
,这有助于简化打包)打包您的项目,并创建控制台脚本入口点;使用 安装您的项目pip
然后创建知道如何正确导入您的包的脚本。您可以使用 本地安装您的包pip install -e .
,因此它仍然可以就地编辑。否则,永远不要使用
python path/to/packagename/file.py
,总是使用python path/to/script.py
并且script.py
可以使用from packagename import ...
。作为后备方案,您可以使用
-m
命令行开关告诉 Python 导入模块并将其用作文件__main__
。但是,这不适用于 shebang 行,因为不再有脚本文件。
如果您使用python -m foo.bar
并且foo/bar.py
位于sys.path
目录中,则 将被导入并执行,就像__main__
使用正确的包上下文一样。如果bar
也是一个包,foo/
则 内部必须有一个__main__.py
文件(因此是目录foo/bar/__main__.py
中的路径sys.path
)。
在极端情况下,通过直接设置添加 Python 用于解析相对导入的元数据
__package__
;文件foo/bar/spam.py
,可导入为foo.bar.spam
,被赋予全局__package__ = "foo.bar"
。它只是Python 在导入时设置的另一个全局变量,如__file__
和。__name__
在sys.path
以上所有操作都要求您的包能够被导入,这意味着它需要在 中列出的目录(或 zip 文件)之一中找到sys.path
。这里也有几个选项:
找到的目录
path/to/script.py
(sopath/to
) 会自动添加到sys.path
。执行python path/to/foo.py
会添加path/to
到sys.path
。如果您打包了您的项目(使用
setuptools
、poetry
或flit
其他 Python 打包工具)并安装了它,那么该包已经被添加到正确的位置。作为最后的手段,为自己添加正确的目录
sys.path
。如果包可以相对于脚本文件定位,请使用__file__
脚本全局命名空间中的变量(例如使用pathlib.Path
对象,HERE = Path(__file__).resolve().parent
它是对文件所在目录的引用,作为绝对路径)。
解决方案 6:
总结
您只能相对导入同一包中的另一个模块内的模块。
概念澄清
我们在书籍/文档/文章中看到很多示例代码,它们向我们展示了如何相对导入模块,但是当我们这样做时,它会失败。
原因很简单,我们没有按照 Python 模块机制的期望运行代码,尽管代码写得完全正确。这就像某种运行时的东西。
模块加载取决于你如何运行代码。这就是混乱的根源。
什么是模块?
当且仅当模块被另一个文件导入时,它才是 Python 文件。给定一个文件mod.py
,它是一个模块吗?是也不是,如果你运行python mod.py
,它就不是一个模块,因为它没有被导入。
什么是包?
包是包含 Python 模块的文件夹。
顺便说一句,__init__.py
如果您不需要任何包初始化或自动加载子模块,则从 python 3.3 开始不需要。您不需要__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
这意味着将模块作为脚本运行。
Python 习语
. 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
我们想要导入sad_maker
,me.py
该怎么做?
首先,我们需要在同一个包中制作happy
和sad
,所以我们必须进入上一级目录。然后from ..sad import sad_maker
在me.py
.
就这样。
解决方案 7:
对于 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 的解析器即可。它是错误的,尽管它说了什么,但代码运行良好。
解决方案 8:
TL;DR:对于@Aya 的回答,使用pathlib
库进行更新,并适用于__file__
未定义的 Jupyter 笔记本:
您想要
根据编写代码的位置导入my_function
定义。../my_Folder_where_the_package_lives/my_package.py
然后执行以下操作:
import os
import sys
import pathlib
PACKAGE_PARENT = pathlib.Path(__file__).parent
#PACKAGE_PARENT = pathlib.Path.cwd().parent # if on jupyter notebook
SCRIPT_DIR = PACKAGE_PARENT / "my_Folder_where_the_package_lives"
sys.path.append(str(SCRIPT_DIR))
from my_package import my_function
解决方案 9:
为了解决这个问题,我设计了一个使用repackage包的解决方案,这个包已经为我工作了一段时间。它将上层目录添加到 lib 路径:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Repackage 可以使用智能策略(检查调用堆栈)在各种情况下进行相对导入。
解决方案 10:
希望这对某些人有用 - 我浏览了六篇 stackoverflow 帖子,试图找出与上面发布的内容类似的相对导入。我按照建议设置了所有内容,但仍然遇到ModuleNotFoundError: No module named 'my_module_name'
由于我只是在本地开发和玩耍,所以我没有创建/运行setup.py
文件。我显然也没有设置我的PYTHONPATH
。
我意识到,当我运行代码时(就像测试与模块位于同一目录中时一样),我找不到我的模块:
$ python3 test/my_module/module_test.py 2.4.0
Traceback (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,请尝试以下任一操作:
运行您的代码并明确包含路径,如下所示:
$ PYTHONPATH=. python3 test/my_module/module_test.py
为了避免调用
PYTHONPATH=.
,请创建一个setup.py
包含以下内容的文件并运行python setup.py development
以将包添加到路径:
# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
解决方案 11:
我的样板在可运行的独立系统中进行module
相对导入package
。
package/module.py
## Standalone boilerplate before relative imports
if __package__ is None:
DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(DIR.parent))
__package__ = DIR.name
from . import variable_in__init__py
from . import other_module_in_package
...
现在您可以以任何方式使用您的模块:
照常运行模块:
python -m package.module
将其用作模块:
python -c 'from package import module'
独立运行:
python package/module.py
或者使用 shebang(
#!/bin/env python
) 即可:package/module.py
注意!如果您的和 同名,使用sys.path.append
代替sys.path.insert
会导致难以追踪的错误。例如module
`package`my_script/my_script.py
当然如果你有来自包层次结构中更高级别的相对导入,那么这还不够,但是对于大多数情况来说,这是可以的。
解决方案 12:
从同一目录导入
首先,您可以从同一目录导入。
这是文件结构...
Folder
|
├─ Scripts
| ├─ module123.py
|
├─ main.py
├─ script123.py
这是main.py
from . import script123
from Scripts import module123
如您所见,.
从当前目录导入。
注意:如果使用 IDLE 以外的任何方式运行,请确保
main.py
在运行之前将终端导航到与文件相同的目录。
此外,从本地文件夹导入也有效。
从父目录导入
正如我在这里的 GitHub 要点中看到的,有以下方法。
以以下文件树为例...
ParentDirectory
├─ Folder
| |
| ├─ Scripts
| | ├─ module123.py
| |
| ├─ main.py
| ├─ script123.py
|
├─ parentModule.py
然后,只需将此代码添加到文件顶部main.py
。
import inspect
import os
import 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
解决方案 13:
我收到此ImportError:尝试进行相对导入,但没有已知父包
在我的程序中,我使用当前路径中的文件来导入其功能。
from .filename import function
然后我用包名修改了当前路径(点)。这解决了我的问题。
from package_name.filename import function
希望以上回答对你有帮助。
解决方案 14:
对于那些不同意Guido 观点的人,这里有三句话:
import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).absolute().parent.parent))
希望有帮助。
更新:发现当通过相对路径调用可执行文件时,此操作会失败。解决方案是使用resolve()
而不是absolute()
:
import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).resolve().parent.parent))
解决方案 15:
我需要从主项目目录运行 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
解决方案 16:
我尝试了以上所有方法但都无济于事,最后我才意识到-
我的包裹名称中有一个错误。
-
简而言之,目录中没有__init__.py
。我从来没有在发现这种无聊的东西后感到高兴过。
解决方案 17:
如果以上方法都不适合您,您可以明确指定模块。
目录:
├── Project
│ ├── Dir
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
解决方案:
#in standalone.py
from Project.Dir.module import ...
module - 要导入的模块
解决方案 18:
如果两个包都在你的导入路径(sys.path)中,并且你想要的模块/类在 example/example.py 中,那么要访问该类而无需相对导入,请尝试:
from example.example import fkt
解决方案 19:
值得注意的是,有时缓存是导致所有问题的原因。
我在将类重新排列到新目录中后尝试了不同的方法,并且在删除 后相对导入开始起作用__pycache__
。
解决方案 20:
我认为最好的解决方案是为您的模块创建一个包:
这里有有关如何执行此操作的更多信息。
一旦您有了一个包,您就不需要担心相对导入,您只需进行绝对导入即可。
解决方案 21:
当我使用 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.py
、dj_app/script.py
和dj_app/tests.py
。我们还有子模块dj_app/ops/bar.py
和,dj_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 classes
foo_val = "foo123"
。
# dj_app/ops/bar.py
# Bar operations methods and classes
bar_val = "bar123"
。
# dj_app/script.py
# script to run app methods from CLI
# if run directly from command line
if __name__ == '__main__':
from ops.bar import bar_val
from ops.foo import foo_val
# otherwise
else:
from .ops.bar import bar_val
from .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 break
from .script import script_method1
from .ops.bar import bar_val
from .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 example
import dj_app.tests
dj_app.tests.main()
。
从运行测试用例manage.py
;
$ python3 manage.py
Running the test case
testing script method
this is script_method1
bar_val: bar123
foo_val: foo123
单独运行脚本;
$ python3 dj_app/script.py
running from the script
this is script_method1
bar_val: bar123
foo_val: foo123
请注意,如果您尝试直接运行,则会收到错误test.py
,所以不要这样做;
$ python3 dj_app/tests.py
Traceback (most recent call last):
File "dj_app/tests.py", line 5, in <module>
from .script import script_method1
ModuleNotFoundError: No module named '__main__.script'; '__main__' is not a package
如果我遇到更复杂的导入情况,我通常会实施类似这样的措施来解决它;
import os
import sys
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, THIS_DIR)
from script import script_method1
sys.path.pop(0)
解决方案 22:
这是我的项目结构
├── folder
| |
│ ├── moduleA.py
| | |
| | └--function1()
| | └~~ uses function2()
| |
│ └── moduleB.py
| |
| └--function2()
|
└── main.py
└~~ uses function1()
这是我的moduleA
进口moduleB
和main
进口moduleA
我添加了下面的代码片段moduleA
来导入moduleB
try:
from .moduleB import function2
except:
from moduleB import function2
现在我可以main.py
同时执行两个操作,也可以moduleA.py
单独执行
这是一个解决方案吗?
解决方案 23:
我遇到了同样的错误,我的项目结构如下
->project
->vendors
->vendors.py
->main.py
我试图这样打电话
from .vendors.Amazon import Amazom_Purchase
这里抛出了一个错误,所以我只需从语句中删除第一个 . 即可修复它
from vendors.Amazon import Amazom_Purchase
希望这有帮助。
解决方案 24:
以下解决方案在Python3上测试过
├── classes
| |
| ├──__init__.py
| |
│ ├── userclass.py
| | |
| | └--viewDetails()
| |
| |
│ └── groupclass.py
| |
| └--viewGroupDetails()
|
└── start.py
└~~ uses function1()
现在,为了使用 userclass 的 viewDetails 或 groupclass 的 viewGroupDetails,首先在 classess 目录的 _ init _.py 中定义它。
例如:在 _ init _.py中
from .userclasss import viewDetails
from .groupclass import viewGroupDetails
步骤2:现在,在start.py中我们可以直接导入viewDetails
例如:在 start.py 中
from classes import viewDetails
from classes import viewGroupDetails
解决方案 25:
当我尝试编写一个可以作为模块或可执行脚本加载的 python 文件时,我遇到了类似的问题。
设置
/path/to/project/
├── __init__.py
└── main.py
└── mylib/
├── list_util.py
└── args_util.py
和:
main.py:
#!/usr/bin/env python3
import sys
import 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 python3
import sys
from . 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 3
mylib.args_util.parseargs(sys.argv[1:])=6
$ mylib/args_util.py 1 2 3
Traceback (most recent call last):
File "/path/to/project/mylib/args_util.py", line 10, in <module>
from . import list_util as lu
ImportError: attempted relative import with no known parent package
解决方案
我选择了 Bash/Python 多语言解决方案。该程序的 Bash 版本只需调用python3 -m mylib.args_util
然后退出即可。
Python 版本忽略了 Bash 代码,因为它包含在文档字符串中。
Bash 版本忽略了 Python 代码,因为它用于exec
停止解析/运行行。
mylib/args_util.py:
#!/bin/bash
# -*- Mode: python -*-
''''true
exec /usr/bin/env python3 -m mylib.args_util "$@"
'''
import sys
from . 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 3
mylib.args_util.parseargs(sys.argv[1:])=6
$ mylib/args_util.py 1 2 3
parseargs(sys.argv[1:])=6
解释
第 1 行:
#!/bin/bash
;这是“shebang”行;它告诉交互式 shell 如何运行该脚本。Python:忽略(评论)
Bash:忽略(评论)
第 2 行:
# -*- Mode: python -*-
可选;这称为“模式行”;它告诉 Emacs 在读取文件时使用 Python 语法高亮,而不是猜测语言是 Bash。Python:忽略(评论)
Bash:忽略(评论)
第 3 行:
''''true
Python:将其视为未分配的文档字符串,开头为`'true
`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 或批处理包装脚本来调用
python -m mylib.args_util
。
这仅当当前工作目录设置为时才有效
/path/to/project/
。解决方法:
PYTHONPATH
调用时设置/usr/bin/env
#!/bin/bash # -*- Mode: python -*- ''''true exec /usr/bin/env python3 \n PYTHONPATH="$(cd "$(dirname "$0")/.." ; pwd)" \n -m mylib.args_util "$@" '''
解决方案 26:
我遇到了类似的问题:我需要一个使用公共常量进行协作的 Linux 服务和 cgi 插件。执行此操作的“自然”方法是将它们放在包的init .py 中,但我无法使用 -m 参数启动 cgi 插件。
我的最终解决方案与上面的解决方案2类似:
import sys
import pathlib as p
import importlib
pp = p.Path(sys.argv[0])
pack = pp.resolve().parent
pkg = importlib.import_module('__init__', package=str(pack))
缺点是必须在常量(或者常用函数)前面加上pkg前缀:
print(pkg.Glob)
解决方案 27:
TLDR;通过在 Python 脚本的入口点添加以下内容将脚本路径附加到系统路径。
import os.path
import sys
PACKAGE_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 以及终端中运行您的项目了!
解决方案 28:
将要导入的文件移到外部目录会有所帮助。
当您的主文件在其自己的目录中创建任何其他文件时,这非常有用。
例如:
之前:
Project
|---dir1
|-------main.py
|-------module1.py
后:
Project
|---module1.py
|---dir1
|-------main.py
解决方案 29:
由于对 setup.py 的误解,我遇到了这个问题。您可能永远不需要这个答案,但是在使用 setup.py 从文件夹安装我的包时,我有这样的配置:
packages=setuptools.find_packages(include=["package_folder.*"])
这是错误的,因为它不应该有
.*
应该是:
packages=setuptools.find_packages(include=["package_folder"])
只是包没有正确安装,与相关导入有偏差。
解决方案 30:
Python 中的相对导入,从一个目录向上
假设您有以下目录结构:
dev/
my_project/
config.py
sandbox/
test.py
并且您想要cd
进入my_project
并运行test.py
,其中test.py
导入config.py
。Python 默认不允许这样做:您无法从当前目录上方导入任何内容,除非您sys.path.insert()
在尝试导入之前先将该上层目录插入到系统路径中。
因此,这是您要拨打的电话:
cd dev/my_project
./sandbox/test.py
下面说明了如何从一个目录test.py
导入:config.py
测试.py:
#!/usr/bin/env python3
import os
import sys
# See: https://stackoverflow.com/a/74800814/4561887
FULL_PATH_TO_SCRIPT = os.path.abspath(__file__)
SCRIPT_DIRECTORY = str(os.path.dirname(FULL_PATH_TO_SCRIPT))
SCRIPT_PARENT_DIRECTORY = str(os.path.dirname(SCRIPT_DIRECTORY))
# allow imports from one directory up by adding the parent directory to the
# system path
sys.path.insert(0, f"{SCRIPT_PARENT_DIRECTORY}")
# Now you can import `config.py` from one dir up, directly!
import config
# The rest of your imports go here...
# The rest of your code goes here...
参考
我最初发布此内容的原始答案:如何获取当前正在执行的 python 文件的路径和名称?
进一步
在 Bash 中,通过使用 可以显得更 Pythonic
if [ "$__name__" = "__main__" ]
,相对导入更加自然,操作如下:[主要资源] 我网站上的文章:GabrielStaples.com:如何在 Bash 中编写、导入、使用和测试库?
我的答案:从 shell 脚本导入函数
我的回答:如何从脚本本身获取 Bash 脚本所在的目录?
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件