通告& python中的嵌套导入

时间:2014-09-12 01:24:29

标签: python import module nested circular-dependency

我现在正面临一些真正的麻烦,试图弄清楚如何正确导入东西。我的应用程序结构如此:

main.py
util_functions.py
widgets/
 - __init__.py
 - chooser.py
 - controller.py

我总是从根目录运行我的应用程序,所以我的大多数导入都是这样的

from util_functions import *
from widgets.chooser import *
from widgets.controller import *
# ...

我的widgets/__init__.py设置如下:

from widgets.chooser import Chooser
from widgets.controller import MainPanel, Switch, Lever

__all__ = [
  'Chooser', 'MainPanel', 'Switch', 'Lever',
]

它工作得很好,除了widgets/controller.py变得有点冗长,我希望它将它分成多个文件:

main.py
util_functions.py
widgets/
 - __init__.py
 - chooser.py
 - controller/
    - __init__.py
    - mainpanel.py
    - switch.py
    - lever.py

问题之一是SwitchLever类具有静态成员,其中每个类需要访问另一个类。使用创建循环导入的from ___ import ___语法导入。因此,当我尝试运行我的重新分解的应用程序时,一切都在进口时破坏了。

我的问题是:如何修复我的导入,以便我可以拥有这个漂亮的项目结构?我无法相互删除SwitchLever的静态依赖关系。

1 个答案:

答案 0 :(得分:2)

这在How can I have modules that mutually import each other下的官方Python常见问题解答中有所介绍。

正如常见问题解答清楚表明的那样,没有任何可以神奇修复问题的银色子弹。 FAQ中描述的选项(比常见问题解答中的更多细节)是:

  • 除了使用常量或内置函数初始化的类,函数和变量,从不from spam import之外,不要在顶层放置任何东西,然后通常不会出现循环导入问题。干净简单,但有些情况下你不能遵守这些规则。
  • 重构模块以将导入移动到模块的中间,其中每个模块定义在导入其他模块之前需要导出的内容。这可能意味着将类拆分为两部分,一个可以在行上方的“接口”类,以及一个位于该行下方的“实现”子类。
  • 以类似的方式重构模块,但将“导出”代码(带有“接口”类)移动到单独的模块中,而不是将它们移到导入之上。然后每个实现模块都可以导入所有接口模块。这与前一个版本具有相同的效果,其优点是您的代码是惯用的,并且人类和自动化工具更易于读取,这些工具可以在模块顶部进行导入,但缺点是您拥有更多模块。

正如常见问题解答所述,“这些解决方案并非相互排斥。”特别是,您可以尝试将尽可能多的顶级代码移动到函数体中,用合理的from spam import …替换尽可能多的import spam语句......然后,如果您仍然具有循环依赖关系,请解决它们通过重构为线上方或单独模块中的无导入导出代码。


考虑到一般性问题,让我们来看看你的具体问题。

您的switch.Switchlever.Lever类有“静态成员,每个类需要访问另一个”。我假设你的意思是他们有使用类属性初始化的类属性或来自其他类的类或静态方法?


在第一个解决方案之后,您可以更改内容,以便在导入时间之后初始化这些值。我们假设您的代码如下所示:

class Lever:
    switch_stuff = Switch.do_stuff()
    # ...

您可以将其更改为:

class Lever:
    @classmethod
    def init_class(cls):
        cls.switch_stuff = Switch.do_stuff()

现在,在__init__.py之后,就在此之后:

from lever import Lever
from switch import Switch

...你补充说:

Lever.init_class()
Switch.init_class()

这就是诀窍:你通过明确初始化并选择一个明确的顺序来解决模糊的初始化顺序。


或者,在第二个或第三个解决方案之后,您可以将Lever分成LeverLeverImpl。然后你这样做(无论是作为单独的lever.pyleverimpl.py文件,还是作为一个中间有导入的文件):

class Lever:
    @classmethod
    def get_switch_stuff(cls):
        return cls.switch_stuff

from switch import Swift

class LeverImpl(Lever):
    switch_stuff = Switch.do_stuff()

现在您不需要任何init_class方法。当然,您确实需要将属性更改为方法 - 但如果您不喜欢这样,通过一些工作,您可以随时将其更改为“类@property”(通过编写自定义描述符) ,或在元类中使用@property


请注意,您实际上不需要修复两个类来解决循环,只需要一个。从理论上讲,修复两者都比较简洁,但在实践中,如果修复很难看,最好修复一个不那么难修复的修复程序,并将依赖项单独留在相反的方向。