Python: 如何重构循环导入

8
我有一个任务需要你完成,使用engine.setState(<state class>)可以实例化给定的类,并在新状态下开始运行。在SelectFileState中有一个按钮可以转到NewFileState,在NewFileState中也有一个按钮可以回到SelectFileState
现在,在SelectFileState的开头,我正在导入NewFileState(以便稍后在类中执行engine.setState(NewFileState))。在NewFileState的开头,我也正在导入SelectFileState(以便稍后返回SelectFileState)。
然而,这会创建循环导入,正如其他帖子中所描述的那样。有人说循环导入是指示不良设计的标志,并且应该进行重构。
我知道我只需在需要使用它之前导入SelectFileState即可解决此问题,但我宁愿以正确的方式重构它。
现在我想知道…您如何重新构造它?谢谢。
编辑: Pydsigner建议我将两个文件合并为一个,因为它们都与彼此非常相关。但是,我不能将每个具有循环依赖关系的状态都放入一个文件中,因此必须有更好的方法。有什么想法吗?
2次编辑: 我通过不使用from x import y语法来规避此问题,而是直接执行import x。这不是一个理想的解决方案,我想知道修复这种问题的“Pythonic”方法。只是合并文件不能永远成为解决方法。
代码:
from states.state import State
from states.newfilestate import NewFileState

from elements.poster import Poster
from elements.label import Label
from elements.button import Button
from elements.trifader import TriFader

import glob
import os

class SelectFileState(State):
    def __init__(self, engine):
        super().__init__(engine)

    def create(self):
        self.engine.createElement((0, 0), Poster(self.engine.getImage('gui_loadsave')), 1)
        self.engine.createElement((168, 30), Label("Load a game", 40), 2)
        self.engine.createElement((400, 470), Button("New save", code=self.engine.createElement, args=((0, 0), TriFader(NewFileState, False), -240)), 3)

        ycounter = 150

        globs = glob.glob("save\\*.mcw")
        for file in globs:
            self.engine.createElement((200, ycounter), Button(os.path.basename(file)[:-4]), 2)
            ycounter += 50

NewFileState

from states.state import State
from states.selectfilestate import SelectFileState

from elements.poster import Poster
from elements.label import Label
from elements.button import Button
from elements.inputbox import InputBox
from elements.trifader import TriFader


class NewFileState(State):
    def __init__(self, engine):
        super().__init__(engine)

    def create(self):
        self.engine.createElement((0, 0), Poster(self.engine.getImage('gui_loadsave')), 1)
        self.engine.createElement((135, 30), Label("Make a new save", 40), 2)

        self.lvlname = self.engine.createElement((180, 212), InputBox(length=25, text="World name"), 2)
        self.engine.createElement((200, 240), Button(text="Ok", code=self.createSave, args=()), 2)

    def createSave(self):
        open("save\\" + self.lvlname.getText() + ".mcw", 'w')
        self.engine.createElement((0, 0), TriFader(SelectFileState), -240)

请参考此答案:https://dev59.com/Tm865IYBdhLWcg3wKLT5#3956038,该答案解释了仅导入命名空间/模块而不使用“from..import”语法可能会有所帮助。 - Brian L
只是一般性的提醒:你真的不需要在这两个类中使用__init__()方法。你所做的只是让你的代码更加冗余和缓慢。 - pydsigner
@pydsigner 是的,我知道。__init__ 方法执行重要任务,例如与引擎进行交互并设置一些变量。 - Name McChange
1
啊,但你忘了你会自动继承父类的方法,包括__init__()。除非你要调整什么,否则不需要覆盖它。 - pydsigner
@pydsigner 哎呀!我早已知道了,但是写成这样已经成为了习惯。感谢你指出来。 - Name McChange
没问题,我们都有自己的坏编程习惯。;-) - pydsigner
4个回答

4

没有看到代码,最合理的做法是将这两个文件合并。如果它们紧密地交织在一起,你可能可以将它们放在一起,而不会出现任何奇怪的地方。


我猜是这样,但这只是一个快速解决方案还是“实际”做法? - Name McChange
我还没有看到你的代码,但一般情况下这表明twain(两者)应该成为一个整体。实际上没有其他好的方法来解决这个问题。循环导入问题是结构问题的一个指示,这也是我试图盲目修复的问题。 - pydsigner
我刚刚添加了它,但我不确定这会如何帮助解释问题。这只是一个更加含糊的解释。 - Name McChange
哦,这确实有帮助。从看到那段代码来看,合并这两个模块是非常明智的事情。它们不是1000行的单体结构,例如导入是相当冗余的。将它们合并。 - pydsigner
我接受了你针对这种特定情况的回答,但我不能将每个状态合并到一个文件中。对于那些不太相关的状态,我该怎么处理呢? - Name McChange
回顾代码后,我认为主要问题在于您遇到了递归UI问题。一个UI允许您创建新的保存,但这将链接到使用另一个UI创建具有不同名称的新保存,而该UI实际上是在第一个UI中使用的。 - pydsigner

2
在Python中,导入语句不必出现在模块的开头。实际上,它们可以出现在函数中。因此,在NewFileState.py中,您可以将SelectFileState的导入移动到NewFileState.create中,并且您可以对SelectFileState.py进行类似的更改。

我在原帖中提到了这一点(我不想这么做)。 - Name McChange
1
这是最简单的修复方法,阅读和维护起来都很容易。 - jsbueno
1
不,导入语句应该放在文件开头(PEP 8规范)。仅仅因为在20行的文件中可行并不意味着这是明智的做法。 - pydsigner

0

在这里你可以做的是,因为只有在代码运行时才需要逆转类,实例化类并调用方法,所以可以将类名可用于父模块-"state",并从那里导入名称:

state.init.py

from states.selectfilestate import SelectFileState
from states.newfilestate import NewFileState

此时,两个子模块都将被初始化——尽管在执行主体期间,它们将“看到”states模块的不完整版本,但是此阶段不会访问该模块。当任何一个类被实例化时,states.__init__.py将已经执行完毕,并且指向states的模块引用将指向完整的模块对象。

在state.selectfilestate中:

import states
...
        self.engine.createElement((0, 0), TriFader(states.SelectFileState), -240)

并且对于 state.newfilestare:

import states
...
        self.engine.createElement((0, 0), TriFader(SelectFileState), -240)

0
你可以将实际的类引用隐藏在一个字典映射中,而不是传递类引用,而是传递一个常量值,它映射到真实的类。这可以仅作为一个独立模块中的dict保留,或者可以包装在单独的StateManager类中,处理检索下一个状态的操作。
这种方法的问题在于,您需要手动更新状态列表和从常量到类引用的映射。
以下是一个示例实现:

states.const

# This module is states.const

(
    STATE_SELECT_FILE,
    STATE_NEW_FILE,
) = range(2) # manually update this number when you add/remove states

states.mapping

# This module is states.mapping
from states.const import *
from states.newfilestate import NewFileState
from states.selectfilestate import SelectFileState

STATE_MAPPING = {
    STATE_SELECT_FILE : SelectFileState,
    STATE_NEW_FILE : NewFileState,
}

选择文件状态

from states.const import STATE_NEW_FILE
# ... snip ...
... TriFader(STATE_NEW_FILE, False) ...

新文件状态

from states.const import STATE_SELECT_FILE
# ... snip ...
... TriFader(STATE_SELECT_FILE) ...

engine.setState()

from states.mapping import STATE_MAPPING

def setState(class_key):
    obj = STATE_MAPPING[class_key]()
    # ... do other stuff ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接