首页 > Python > 我想从同一目录中的另一个文件导入函数?

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

上一篇 下一篇

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

通常,以下工作之一:

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

这是为什么呢?

分割线

网友回答:

解释

从 PEP 328

相对导入使用模块的 __name__ 属性来确定该
模块在包层次结构中的位置。如果模块的名称
不包含任何包信息(例如,它设置为“__main__”),
相对导入将像模块是顶级
模块一样解析
,而不管模块在文件系统
上的实际位置如何。

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

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

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

通过添加新的模块级别属性,如果使用 -m
开关执行模块,此 PEP 允许相对
导入自动工作。模块本身中的少量样板将允许
在按名称执行文件时相对导入工作。[…]当它 [属性] 存在时,相对导入将基于此属性
而不是模块__name__属性。[…]当主模块由其文件名指定时,__package__ 属性将设置为 None。[…]当导入系统在
模块中遇到显式相对导入而未设置__package__(或设置为 None)时,它将
计算并存储正确的值
__name__.rpartition(’.’)。[0]
用于普通模块
__name__用于包初始化模块)

(强调我的)

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

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

CPython职能的相关部分:PyImport_ImportModuleLevelObject

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

如果 CPython 无法在 (可访问为 ) 中找到(包的名称),则会引发此异常。由于是“将模块名称映射到已加载模块的字典”,现在很清楚在执行相对导入之前必须显式绝对导入父模块packageinterp->modulessys.modulessys.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

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

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

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

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

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

from . import module  # explicit relative import

我们很清楚这将失败。但是,我们可以使用命令行选项运行该模块,该选项将“搜索 sys.path 的命名模块并将其内容作为__main__模块执行”/path/to/python/interpreter package/standalone.py-m

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

因此

  1. 将当前模块的第 N 个前置任务的父目录添加到sys.path
  2. 从中删除当前文件的目录sys.path
  3. 使用当前模块的完全限定名称导入其父模块
  4. 设置为 2 中的完全限定名称__package__
  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 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:使用绝对导入和设置工具

步骤是 –

  1. 将显式相对导入替换为等效的绝对导入
  2. 安装以使其可导入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

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

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

  1. 在尝试使用绝对导入从包导入任何内容之前,将的父目录添加到: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
    
  2. 将相对导入替换为绝对导入:
    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.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()

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

from .mymodule import as_int

你应该运行它的方式是…

python3 -m mypackage.myothermodule

…但它有点啰嗦,与像 shebang 这样的 shebang 台词不能很好地混合。#!/usr/bin/env python3

在这种情况下,假设名称是全局唯一的,最简单的解决方法是避免使用相对导入,而只使用…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

这有点痛苦,但有一个线索,为什么在某位圭多·范·罗森写的电子邮件中……

我对此和任何其他提议的
机器摆动都是 -1。唯一的用例似乎是运行恰好
位于模块目录中的脚本,我一直将其视为
反模式。要让我改变主意,你必须让我相信
事实并非如此。
__main__

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

分割线

网友回答:

把它放在包的__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。

模板简介:该模板名称为【我想从同一目录中的另一个文件导入函数?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【Python】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 189次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 03-05
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
html5 微信模板 微信素材 微信公众平台 响应式 单页式简历模板 自适应 微信文章 微信图片 企业网站
您可能会喜欢的其他模板