我现在正面临一些真正的麻烦,试图弄清楚如何正确导入东西。我的应用程序结构如此:
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
问题之一是Switch
和Lever
类具有静态成员,其中每个类需要访问另一个类。使用创建循环导入的from ___ import ___
语法导入。因此,当我尝试运行我的重新分解的应用程序时,一切都在进口时破坏了。
我的问题是:如何修复我的导入,以便我可以拥有这个漂亮的项目结构?我无法相互删除Switch
和Lever
的静态依赖关系。
答案 0 :(得分:2)
这在How can I have modules that mutually import each other下的官方Python常见问题解答中有所介绍。
正如常见问题解答清楚表明的那样,没有任何可以神奇修复问题的银色子弹。 FAQ中描述的选项(比常见问题解答中的更多细节)是:
from spam import
之外,不要在顶层放置任何东西,然后通常不会出现循环导入问题。干净简单,但有些情况下你不能遵守这些规则。正如常见问题解答所述,“这些解决方案并非相互排斥。”特别是,您可以尝试将尽可能多的顶级代码移动到函数体中,用合理的from spam import …
替换尽可能多的import spam
语句......然后,如果您仍然具有循环依赖关系,请解决它们通过重构为线上方或单独模块中的无导入导出代码。
考虑到一般性问题,让我们来看看你的具体问题。
您的switch.Switch
和lever.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分成Lever
和LeverImpl
。然后你这样做(无论是作为单独的lever.py
和leverimpl.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
。
请注意,您实际上不需要修复两个类来解决循环,只需要一个。从理论上讲,修复两者都比较简洁,但在实践中,如果修复很难看,最好修复一个不那么难修复的修复程序,并将依赖项单独留在相反的方向。