首页  >  文章  >  后端开发  >  在 Python 中修复循环导入的不同方法

在 Python 中修复循环导入的不同方法

Linda Hamilton
Linda Hamilton原创
2024-11-05 02:21:01784浏览

你在Python中遇到过循环导入吗?嗯,这是一种非常常见的代码味道,表明设计或结构有问题。

循环导入示例

循环导入是如何发生的?当两个或多个相互依赖的模块在完全初始化之前尝试导入时,通常会发生此导入错误。

假设我们有两个模块:module_1.py 和 module_2.py。

# module_1.py
from module_2 import ModY
class ModX:
    mody_obj = ModY()
# module_2.py
from module_1 import ModX
class ModY:
    modx_obj = ModX()

在上面的代码片段中,module_1和module_2都是相互依赖的。

module_1 中 mody_obj 的初始化依赖于 module_2,module_2 中 modx_obj 的初始化依赖于 module_1。

这就是我们所说的循环依赖。两个模块在尝试互相加载时都会陷入导入循环。

如果我们运行 module_1.py,我们将得到以下回溯。

Traceback (most recent call last):
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
  File "module_2.py", line 1, in <module>
    from module_1 import ModX
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
ImportError: cannot import name 'ModY' from partially initialized module 'module_2' (most likely due to a circular import)

这个错误说明了循环导入的情况。当程序尝试从 module_2 导入 ModY 时,当时 module_2 尚未完全初始化(由于另一个导入语句尝试从 module_1 导入 ModX)。

如何修复 Python 中的循环导入? ​​有多种方法可以消除 Python 中的循环导入。

修复 Python 中的循环导入

将代码移至公共文件中

我们可以将代码移动到一个公共文件中以避免导入错误,然后尝试从该文件导入模块。

# main.py ----> common file
class ModX:
    pass

class ModY:
    pass

在上面的代码片段中,我们将类 ModX 和 ModY 移动到一个公共文件 (main.py) 中。

# module_1.py
from main import ModY

class Mod_X:
    mody_obj = ModY()
# module_2.py
from main import ModX

class Mod_Y:
    modx_obj = ModX()

现在,module_1 和 module_2 从 main 导入类,修复了循环导入的情况。

这种方法有一个问题,有时代码库太大,将代码移动到另一个文件中会存在风险。

将导入移至模块末尾

我们可以将导入语句移到模块末尾。这将为导入另一个模块之前留出时间来完全初始化模块。

# module_1.py
class ModX:
   pass

from module_2 import ModY

class Mod_X:
   mody_obj = ModY()
# module_2.py
class ModY:
   pass

from module_1 import ModX

在类/函数范围内导入模块

在类或函数作用域内导入模块可以避免循环导入。这允许仅在调用类或函数时导入模块。当我们想要最小化内存使用时,它是相关的。

# module_1.py
class ModX:
  pass

class Mod_X:
   from module_2 import ModY
   mody_obj = ModY()
# module_2.py
class ModY:
   pass

class Mod_Y:
   from module_1 import ModX
   modx_obj = ModX()

我们分别将 import 语句移至 module_1 和 module_2 中的类 Mod_X 和 Mod_Y 范围内。

如果我们运行 module_1 或 module_2,我们不会收到循环导入错误。但是,这种方法使得类只能在类的范围内访问,因此我们无法全局利用导入。

使用模块名称/别名

使用模块名称或仅像这样的别名可以解决问题。这允许两个模块通过将循环依赖推迟到运行时来完全加载。

# module_1.py
from module_2 import ModY
class ModX:
    mody_obj = ModY()
# module_2.py
from module_1 import ModX
class ModY:
    modx_obj = ModX()

使用 importlib 库

我们还可以使用 importlib 库动态导入模块。

Traceback (most recent call last):
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
  File "module_2.py", line 1, in <module>
    from module_1 import ModX
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
ImportError: cannot import name 'ModY' from partially initialized module 'module_2' (most likely due to a circular import)
# main.py ----> common file
class ModX:
    pass

class ModY:
    pass

Python 包中的循环导入

通常,循环导入 来自同一包内的模块。在复杂的项目中,目录结构也很复杂,包内包。

这些包和子包包含 __init__.py 文件,以提供更轻松的模块访问。这就是有时无意中在模块之间产生循环依赖的地方。

我们有以下目录结构。

# module_1.py
from main import ModY

class Mod_X:
    mody_obj = ModY()

我们有一个 mainpkg 包和一个 main.py 文件。我们在 mainpkg 中有两个子包 modpkg_x 和 modpkg_y。

这是 modpkg_x 和 modpkg_y 中每个 Python 文件的样子。

mainpkg/modpkg_x/__init__.py

# module_2.py
from main import ModX

class Mod_Y:
    modx_obj = ModX()

此文件从 module_1 和 module_1_1 导入两个类(ModX 和 ModA)。

mainpkg/modpkg_x/module_1.py

# module_1.py
class ModX:
   pass

from module_2 import ModY

class Mod_X:
   mody_obj = ModY()

module_1 从 module_2 导入 ModY 类。

mainpkg/modpkg_x/module_1_1.py

# module_2.py
class ModY:
   pass

from module_1 import ModX

module_1_1 没有导入任何内容。它不依赖于任何模块。

mainpkg/modpkg_y/__init__.py

# module_1.py
class ModX:
  pass

class Mod_X:
   from module_2 import ModY
   mody_obj = ModY()

此文件从 module_2 导入 ModY 类。

mainpkg/modpkg_y/module_2.py

# module_2.py
class ModY:
   pass

class Mod_Y:
   from module_1 import ModX
   modx_obj = ModX()

module_2 从 module_1_1 导入 ModA 类。

我们在 main.py 文件中有以下代码。

root_dir/main.py

# module_1.py
import module_2 as m2

class ModX:
    def __init__(self):
        self.mody_obj = m2.ModY()

主文件从 module_2 导入 ModY 类。该文件依赖于 module_2。

如果我们在这里可视化导入周期,它看起来像下面忽略 modpkg_x 和 modpkg_y 中的 __init__.py 文件。

Different Ways to Fix Circular Imports in Python

我们可以看到主文件依赖于module_2,module_1也依赖于module_2,module_2又依赖于module_1_1。没有导入周期。

但是你知道,模块依赖于它们的 __init__.py 文件,因此 __init__.py 文件首先初始化,然后重新导入模块。

Different Ways to Fix Circular Imports in Python

这就是现在的导入周期。

Different Ways to Fix Circular Imports in Python

这使得 module_1_1 依赖于 module_1,这是一个假依赖项。

如果是这种情况,清空子包 __init__.py 文件并使用单独的 __init__.py 文件可以帮助在包级别集中导入。

# module_1.py
from module_2 import ModY
class ModX:
    mody_obj = ModY()

在此结构中,我们在 mainpkg 中添加了另一个子包 subpkg。

mainpkg/subpkg/__init__.py

# module_2.py
from module_1 import ModX
class ModY:
    modx_obj = ModX()

这将允许内部模块从单一来源导入,从而减少交叉导入的需要。

现在我们可以更新 main.py 文件中的导入语句。

root_dir/main.py

Traceback (most recent call last):
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
  File "module_2.py", line 1, in <module>
    from module_1 import ModX
  File "module_1.py", line 1, in <module>
    from module_2 import ModY
ImportError: cannot import name 'ModY' from partially initialized module 'module_2' (most likely due to a circular import)

这解决了同一包内模块之间的循环依赖问题。

结论

Python 中的循环依赖或导入是一种代码异味,这表明需要对代码进行认真的重构和重构。

您可以尝试上述任何一种方法来避免 Python 中的循环依赖。


如果您喜欢这篇文章,您可能还会感兴趣

✅Flask 中的模板继承示例。

✅exec() 和 eval() 之间的区别并举例。

✅了解Python中global关键字的使用。

✅Python 类型提示:函数、返回值、变量。

✅为什么在函数定义中使用斜线和星号。

✅学习率如何影响 ML 和 DL 模型?


现在就这些。

继续编码✌✌。

以上是在 Python 中修复循环导入的不同方法的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn