在 Jython 中使用 getattr 时出现 StackOverflowError

StackOverflowError when using getattr in Jython

我正在用 Jython 编写一个文本编辑器。这个文本编辑器有一个工具栏,它用 ToolbarView class 显示并由 ToolbarController class 处理。 ToolbarController 自己无法处理某些操作,因此将这些操作委托给 MainController class.

为了避免重复代码,因为有许多操作从 ToolbarController 委派给了 MainController,我使用了 getattr,正如我在上一个问题 中所建议的那样。我还意识到我可以在 ToolbarView 代码中使用相同的机制来实现按钮的操作,但我无法让它工作,我最终得到一个无限循环和 Java WhosebugError

这是相关代码的摘录:

工具栏视图 class:

from javax.swing import JToolBar, ImageIcon, JButton

class ToolbarView(JToolBar):

    def __init__(self, controller):

        #Give reference to controller to delegate action response
        self.controller = controller

        options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
        for option in options:
            methods[option] = "on" + option + "Click"
            print methods[option]

        for name, method in methods.items():
            button = JButton(name, actionPerformed=getattr(self, method))
            self.add(button)

    def __getattr__(self, name):
        return getattr(self.controller, name)

工具栏控制器 class:

from .ToolbarView import ToolbarView
class ToolbarController(object):

    def __init__(self, mainController):

        #Create view with a reference to its controller to handle events
        self.view = ToolbarView(self)

        #Will also need delegating to parent presenter
        self.mainController = mainController

    def __getattr__(self, name):
        return getattr(self.mainController, name)

主控制器 class:

from .ToolbarController import ToolbarController

class MainController(object):

    def __init__(self):
        self.toolbarController = ToolbarController(self)

    def onNewFileClick(self, event):
        print("MainController: Creating new file...")

    def onEditFileClick(self, event):
        print("MainController: Editting new file...")

    def onSaveFileClick(self, event):
        print("MainController: Saving new file...")

    def onCloseFileClick(self, event):
        print("MainController: Closing new file...")

所以我期望的是当我单击按钮时,MainController.onNewFileClick 被执行并在控制台中打印出该消息。如果我想从 ToolbarView 委托给 ToolbarController,它会起作用,但是当我将该委托从 ToolbarController 传递给 MainController 时,它不起作用。它似乎在无限循环中调用自己。我得到的错误是:

Traceback (most recent call last):
  File "main.py", line 3, in <module>
    MainController()
  File "/home/training/Jython/controller/MainController", line 8, in __init__
    self.toolbarController = ToolbarController(self)
  File "/home/Jython/controller/ToolbarController.py", line 8, in __init__
    self.view = ToolbarView(self)
  File "/home/Jython/controller/ToolbarView.py", line 44, in __init__
    button = JButton(name, actionPerformed=getattr(self, method))
  File "/home/Jython/controller/ToolbarView.py", line 54, in __getattr__
    return getattr(self.controller, name)
  File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
    return getattr(self.mainController, name)
  File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
    return getattr(self.mainController, name)

[...]

  File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
    return getattr(self.mainController, name)
RuntimeError: maximum recursion depth exceeded (Java WhosebugError)

我做错了什么?我在 python 中尝试过类似的东西(从 class 委托到另一个 class 到另一个 class)并且如果在 getattr 之后放置 () 它会起作用,但在这里我因为 JButton 中的 actionPerformed 而感到困惑。我试过了,但结果是一样的。

您似乎在使用 Jython,我不太清楚。无论如何,在 python 中,您覆盖了 __getattr__,那么您应该期望 getattr 改用您覆盖的钩子。所以我认为你的意思是:

class ToolbarView(JToolBar):

    def __init__(self, controller):

        #Give reference to controller to delegate action response
        self.controller = controller

        options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
        for option in options:
            methods[option] = "on" + option + "Click"
            print methods[option]

        for name, method in methods.items():
            button = JButton(name, actionPerformed=super(ToolbarView, self).__getattr__(method))
            self.add(button)

    def __getattr__(self, name):
        return getattr(self.controller, name)

观看按钮是如何创建的。

就您遇到 SO 问题的原因而言,这是因为 getattr 的处理方式。如果你覆盖 __getattr__,这个钩子只会在你试图引用一个未定义的字段时被调用:

>>> class A(object):
    defined = True
    def __getattr__(self, name):
        print "referenced :" + name


>>> a = A()
>>> a.defined
True
>>> a.undefined
referenced :undefined

希望钩子现在是如何工作的。

所以 SO 实际上是由于您引用了不属于 MainController 的内容造成的。

在您的 MainController 中,仅定义了 onNewFileClick,但您定义了其他 3 个选项:

options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']

因此,这将发生在第二轮迭代中。由于 MainController 没有 onOpenFileClickAttributeError 将被引发,但被 ToolbarController 捕获,因此被覆盖的 __getattr__ 被调用并不断调用。这就是你的调用堆栈爆炸的原因。

我把这归咎于 getattr 因为我还没有那么自信地使用它,但事实证明它是相当基本的东西。

我正在将 mainController 分配给 ToolbarController AFTER 创建 ToolbarView 然后调用 ToolbarView.__getatrr__,它调用 ToolbarController.__getattr__ 试图访问尚不存在的 self.mainController

这是我需要在 ToolbarController class 中进行的更改。

之前

from .ToolbarView import ToolbarView
class ToolbarController(object):

    def __init__(self, mainController):

        #Create view with a reference to its controller to handle events
        self.view = ToolbarView(self)

        #Will also need delegating to parent presenter
        self.mainController = mainController

    def __getattr__(self, name):
        return getattr(self.mainController, name)

之后:

from .ToolbarView import ToolbarView
class ToolbarController(object):

    def __init__(self, mainController):

        #Needs to delegate to main presenter. 
        #Note self.mainController needs to exist before creating the ToolbarView
        #since it needs delegating actions to it!
        self.mainController = mainController

        #Create view with a reference to its controller to handle events
        self.view = ToolbarView(self)

    def __getattr__(self, name):
        return getattr(self.mainController, name)

非常感谢@HuStmpHrrr 和@ArtOfWarfare 的帮助。