我想从同一目录中的另一个文件导入一个函数。
有时它对我有用 from .mymodule import myfunction
但有时我会得到:
SystemError: Parent module '' not loaded, cannot perform relative import
有时它适用于 from mymodule import myfunction
,但有时我也会得到:
SystemError: Parent module '' not loaded, cannot perform relative import
我不明白这里的逻辑,我找不到任何解释。这看起来完全随机。
有人可以向我解释这一切背后的逻辑是什么吗?
不幸的是,这个模块需要在包中,有时它还需要作为脚本运行。知道我怎么能做到这一点吗?
有这样的布局很常见......
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
你应该运行它的方式是......
python3 -m mypackage.myothermodule
...但它有点冗长,并且不能与 #!/usr/bin/env python3
之类的 shebang 行很好地混合。
对于这种情况,最简单的解决方法是,假设名称 mymodule
是全局唯一的,将避免使用相对导入,而只需使用...
from mymodule import as_int
...虽然,如果它不是唯一的,或者您的包结构更复杂,您需要在 PYTHONPATH
中包含包含您的包目录的目录,并这样做...
from mypackage.mymodule import as_int
...或者,如果您希望它“开箱即用”,您可以首先使用代码中的 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 所写的an email中......
我对此和任何其他提议的 __main__ 机器的玩弄都是-1。唯一的用例似乎是运行恰好位于模块目录中的脚本,我一直将其视为反模式。要让我改变主意,你必须说服我它不是。
在包中运行脚本是否是反模式是主观的,但我个人发现它在我拥有的包含一些自定义 wxPython 小部件的包中非常有用,因此我可以为任何源文件运行脚本以显示 {1 } 仅包含用于测试目的的小部件。
解释
来自PEP 328
相对导入使用模块的 __name__ 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'__main__'),那么无论模块实际位于文件系统上的什么位置,相对导入都会像模块是顶级模块一样被解析。
...相对导入依赖 __name__ 来确定当前模块在包层次结构中的位置。在主模块中,__name__ 的值始终为 '__main__',因此显式相对导入总是会失败(因为它们仅适用于包内的模块)
为了解决这个问题,PEP 366 引入了顶级变量 __package__
:
通过添加新的模块级别属性,如果使用 -m 开关执行模块,此 PEP 允许相对导入自动工作。当文件按名称执行时,模块本身的少量样板将允许相对导入工作。 [...]当 [属性] 存在时,相对导入将基于此属性而不是模块 __name__ 属性。 [...] 当主模块由其文件名指定时,__package__ 属性将设置为 None。 [...]当导入系统在没有设置 __package__ (或设置为 None )的模块中遇到显式相对导入时,它将计算并存储正确的值 (__name__.rpartition('.')[0] for普通模块和 __name__ 用于包初始化模块)
(强调我的)
如果 __name__
是 '__main__'
,则 __name__.rpartition('.')[0]
返回空字符串。这就是错误描述中有空字符串文字的原因:
SystemError: Parent module '' not loaded, cannot perform relative import
CPython 的 PyImport_ImportModuleLevelObject
function 的相关部分:
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
(可作为 sys.modules
访问)中找不到 package
(包的名称),则会引发此异常。由于 sys.modules
是“将模块名称映射到已加载的模块的字典”,现在很清楚 在执行相对导入之前必须明确地绝对导入父模块强>。
注意:来自 issue 18018 的补丁添加了 another if
block,它将在上述代码之前执行:
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):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
包中的所有文件都以相同的 2 行代码开头:
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
command line option 运行模块,该模块将“在 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 中删除当前文件的目录 使用其完全限定名称导入当前模块的父模块 将 __package__ 设置为来自 2 的完全限定名称相对进口
我将从解决方案 #1 中借用文件并添加更多子包:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
这次standalone.py 将使用以下相对导入从包包中导入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
可以在 here 中找到包含在函数中的更通用的解决方案。示例用法:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
解决方案 #3:使用绝对导入和 setuptools
步骤是 -
用等效的绝对导入替换显式相对导入安装包以使其可导入
例如,目录结构可能如下
.
├── 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
(--user
将软件包安装在 your site-packages directory 中)(步骤 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'>
注意:如果您决定走这条路,最好使用 virtual environments 单独安装软件包。
解决方案#4:使用绝对导入和一些样板代码
坦率地说,安装不是必需的 - 您可以在脚本中添加一些样板代码以使绝对导入工作。
我将从解决方案#1 中借用文件并更改standalone.py:
在尝试使用绝对导入从包中导入任何内容之前,将包的父目录添加到 sys.path: import sys from pathlib import Path # 如果您还没有这样做 file = Path(__file__).resolve() parent, root = file.parent, file.parents[1] sys.path.append(str(root)) # 另外从 sys.path 中删除当前文件的目录 try: sys.path.remove(str(parent)) except ValueError: # Already移除 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 建议使用绝对导入,但指出在某些情况下显式相对导入是可以接受的:
建议使用绝对导入,因为它们通常更具可读性并且往往表现更好(或至少提供更好的错误消息)。 [...] 但是,显式相对导入是绝对导入的可接受替代方案,特别是在处理复杂的包布局时,使用绝对导入会不必要地冗长。
__main__
,是否可以手动设置 __package__
以解决问题?
__package__
的值同名,这将失败,因为您尝试运行的文件将然后优先并被导入而不是包。
python -m
为我工作
把它放在你包的 __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 和 3。
__init__.py
中基本上可以解决所有相关的导入错误。
sys.path
,因为我担心它可能会影响其他代码。 (部分原因是我不知道它如何工作的复杂性。)
我遇到了这个问题。 hack 解决方法是通过 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()
except:
很糟糕。请改用 except ImportError:
!
SystemError
。 (Py 3.4)
if __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int
之类的东西。
setup.py
所在的包根目录中的 pip install -e .
)来测试它,并将它作为一个包导入到您的测试文件中,而不是乱七八糟像这样有条件的进口。
SystemError:父模块''未加载,无法执行相对导入
这意味着您正在将包内的模块作为脚本运行。在包中混合脚本是棘手的,应尽可能避免。改用导入包并运行 scripty
函数的包装脚本。
如果您的顶级目录称为 foo
,它位于您的 PYTHONPATH
模块搜索路径中,并且您在其中有一个包 bar
(它是您希望在其中包含 __init__.py
文件的目录),脚本不应放在 bar
内,而应在 foo
最多内继续存在。
请注意,scripts 与此处的 modules 不同之处在于它们用作 python
命令的文件名参数,可以使用 python <filename>
或通过 #!
(社邦)线。它直接作为 __main__
module 加载(这就是 if __name__ == "__main__":
在脚本中起作用的原因),并且没有包上下文可用于相对导入。
您的选择
如果可以的话,使用 setuptools(或诗歌或 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 目录的路径)。
在极端情况下,通过直接设置 __package__ 添加 Python 用于解析相对导入的元数据;文件 foo/bar/spam.py,可作为 foo.bar.spam 导入,被赋予全局 __package__ = "foo.bar"。它只是另一个全局变量,如 __file__ 和 __name__,由 Python 在导入时设置。
在 sys.path 上
以上所有要求您的包可以导入,这意味着它需要在 sys.path
中列出的目录(或 zipfile)之一中找到。这里也有几个选项:
找到 path/to/script.py 的目录(因此 path/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 是对文件目录的引用生活在,作为绝对路径)。
对于 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 的解析器。这是错误的,尽管它说了什么,但代码运行良好。
try: from .mname import symbol except: from mname import symbol
为了避免这个问题,我设计了一个使用 repackage 包的解决方案,它已经为我工作了一段时间。它将上层目录添加到 lib 路径:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Repackage 可以使用智能策略(检查调用堆栈)进行适用于各种情况的相对导入。
ImportError: attempted relative import with no known parent package
up()
在目录层次结构中只进入一级。您需要检查您在那里实际找到的内容。
sys.path.append
具有相同的效果,以在搜索路径中添加“包含文件夹”。然后绝对路径起作用。
希望这对那里的人有价值 - 我浏览了六篇 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() )
TL;DR:对@Aya 的回答,使用 pathlib 库进行了更新,并适用于未定义 __file__ 的 Jupyter 笔记本:
您想要导入在 ../my_Folder_where_the_package_lives/my_package.py
下定义的 my_function
,这与您编写代码的位置有关。
然后做:
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
pathlib.Path.cwd().parent
而不是 Path.cwd().parent
?
我需要从主项目目录运行 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
我在可独立运行的 package
中制作具有相对导入的 module
的样板。
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) 只需:包/模块.py
注意!如果您的 module
与您的 package
同名,则使用 sys.path.append
而不是 sys.path.insert
会给您带来难以追踪的错误。例如my_script/my_script.py
当然,如果您有来自包层次结构中更高级别的相对导入,那么这还不够,但在大多数情况下,这还可以。
if not __package__: [set __package__]
.parent
,但这是针对我的嵌套包的情况。无论如何,谢谢!
我收到了这个 ImportError:尝试使用没有已知父包的相对导入
在我的程序中,我使用当前路径中的文件来导入其功能。
from .filename import function
然后我用包名修改了当前路径(点)。这解决了我的问题。
from package_name.filename import function
希望以上回答对你有所帮助。
package_name
是什么?
我尝试了以上所有方法都无济于事,只是意识到我错误地在我的包名中有一个 -
。
简而言之,__init__.py
所在的目录中没有 -
。在发现这种愚蠢之后,我从未感到高兴。
-
是非法的,或者至少不赞成
从同一目录导入
首先,您可以从同一目录导入。
这是文件结构...
Folder
|
├─ Scripts
| ├─ module123.py
|
├─ main.py
├─ script123.py
这是main.py
from . import script123
from Scripts import module123
如您所见,从 .
导入从当前目录导入。
注意:如果使用除 IDLE 之外的任何东西运行,请确保在运行之前将您的终端导航到与 main.py 文件相同的目录。
此外,从本地文件夹导入也可以。
从父目录导入
如my GitHub gist here所示,有以下方法。
采取以下文件树...
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
如果两个包都在您的导入路径 (sys.path) 中,并且您想要的模块/类在 example/example.py 中,那么要在没有相对导入的情况下访问该类,请尝试:
from example.example import fkt
如果上述方法都不适合您,您可以明确指定模块。
目录:
├── Project
│ ├── Dir
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
解决方案:
#in standalone.py
from Project.Dir.module import ...
module - 要导入的模块
我在使用 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)
这是我的项目结构
├── 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
这是一个解决方案吗?
sys.version_info[0]
来检查您是在 python 2 还是 3 上,或者我会将异常绑定到 ImportError
。尽管在 python 3 中,相对路径可能不是 python 包,因此该解决方案也可能不起作用。这取决于模块B的位置。 (按照惯例,模块也应该是蛇形的)。
以下解决方案在 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
Step2:现在,在start.py中我们可以直接导入viewDetails
例如:在 start.py
from classes import viewDetails
from classes import viewGroupDetails
我有一个类似的问题:我需要一个使用通用常量来配合的 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)
将要从中导入的文件移动到外部目录会有所帮助。当您的主文件在其自己的目录中创建任何其他文件时,这非常有用。例如:之前:项目 |---dir1 |-------main.py |-------module1.py 之后:项目 |---module1.py |---dir1 |-- -----main.py
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 以及终端中运行您的项目!
在尝试编写可以作为模块或可执行脚本加载的 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:忽略(评论)
Python:忽略(评论)
Bash:忽略(评论)
第 2 行:# -*- 模式:python -*- 可选;这称为“模式线”;它告诉 Emacs 在读取文件时使用 Python 语法高亮而不是猜测语言是 Bash。 Python:忽略(评论) Bash:忽略(评论)
Python:忽略(评论)
Bash:忽略(评论)
第 3 行:''''true Python:将其视为以 'true 开头的未分配文档字符串\n Bash:将其视为扩展为 true 的三个字符串(其中前两个是空字符串)(即 '' + '' +“真”=“真”);然后它运行为真(什么都不做)并继续到下一行
Python:将其视为以 'true\n 开头的未分配文档字符串
Bash:将其视为扩展为 true 的三个字符串(其中前两个是空字符串)(即 '' + '' + 'true' = 'true');然后它运行为真(什么都不做)并继续到下一行
第 4 行: exec /usr/bin/env python3 -m mylib.args_util "$@" Python:仍然将其视为第 3 行的文档字符串的一部分。 Bash:运行 python3 -m mylib.args_util 然后退出(它没有解析超出此行的任何内容)
Python:仍然将其视为第 3 行的文档字符串的一部分。
Bash:运行 python3 -m mylib.args_util 然后退出(它不会解析超出此行的任何内容)
第 5 行:''' Python:将此视为第 3 行的文档字符串的结尾。 Bash:不解析此行
Python:将其视为第 3 行的文档字符串的结尾。
Bash:不解析这一行
注意事项
这在 Windows 上不起作用: 解决方法:使用 WSL 或批处理包装器脚本调用 python -m mylib.args_util。
解决方法:使用 WSL 或批处理包装器脚本调用 python -m mylib.args_util。
这仅在当前工作目录设置为 /path/to/project/ 时才有效。解决方法:调用 /usr/bin/env #!/bin/bash # -*- Mode: python -*- ''''true exec /usr/bin/env python3 \ PYTHONPATH="$(cd "$ (dirname "$0")/.." ; pwd)" \ -m mylib.args_util "$@" '''
解决方法:调用 /usr/bin/env #!/bin/bash # -*- Mode: python -*- ''''true exec /usr/bin/env python3 \ PYTHONPATH="$(cd "$ (dirname "$0")/.." ; pwd)" \ -m mylib.args_util "$@" '''
我为 Python 创建了一个新的实验性导入库:ultraimport
它使程序员可以更好地控制导入并使它们明确。当导入失败时,它还会提供更好的错误消息。
它允许您执行始终有效的相对、基于文件系统的导入,无论您如何运行代码,也无论您当前的工作目录是什么。运行脚本或模块都没有关系。您也不必更改可能有其他副作用的 sys.path。
然后你会改变
from .mymodule import myfunction
至
import ultraimport
myfunction = ultraimport('__dir__/mymodule.py', 'myfunction')
这样,即使您将代码作为脚本运行,导入也将始终有效。
像这样导入脚本时的一个问题是后续的相对导入可能会失败。 ultraimport 有一个内置的预处理器来自动重写相关的导入。
# so that it can be ran as a standalone script with Kernel set in project directory
if __name__ == "__main__":
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from other_folder.other_file import useful_function
def hello_world():
useful_function()
print("hello world")
if __name__ == "__main__":
hello_world()
这是我的解决方案。我既可以将其作为独立脚本运行,也可以将其作为包的一部分导入而不会出现问题。
很简单,只有当您尝试将项目目录作为独立脚本运行时,它才会将项目目录添加到您的路径中
我有一个类似的问题,并通过在工作目录中创建一个指向包的符号链接来解决它:
ln -s ../../../my_package my_package
然后像往常一样导入它:
import my_package
我知道这更像是“Linux”解决方案而不是“Python”解决方案。但它仍然是一种有效的方法。
不定期副业成功案例分享
file
,那么您也可以使用os.path.realpath(os.path.dirname(__file__))
,在 a comment of Import a module from a relative path 中以os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))
形式给出了获取 SCRIPTDIR 的更好方法。os.path.abspath()
而不是os.path.realpath()
。很少需要解析路径上的所有符号链接,这样做实际上会破坏高级符号链接的使用,以便在单个“虚拟”目录中收集正确的包。