在Jython

时间:2015-09-30 14:59:32

标签: python model-view-controller jbutton jython getattr

我正在Jython中编写一个文本编辑器。此文本编辑器有一个工具栏,显示为ToolbarView类,由ToolbarController类处理。 ToolbarController本身无法处理某些操作,因此会将这些操作委托给MainController类。

为了避免重复代码,因为有很多操作从ToolbarController委托给MainController,我使用了getattr,就像我之前提出的问题here中所建议的那样。我也意识到我可以在ToolbarView代码中使用相同的机制来处理按钮的操作,但我无法使其工作,最终得到一个无限循环和Java StackOverflowError

这是相关代码的摘录:

工具栏视图类:

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)

ToolbarController 类:

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)

MainController 类:

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 StackOverflowError)

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

2 个答案:

答案 0 :(得分:1)

似乎你正在使用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没有onOpenFileClick,因此AttributeError会被引发,但会被ToolbarController捕获,因此会一直调用被覆盖的__getattr__。这就是你的调用栈爆炸的原因。

答案 1 :(得分:1)

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

我将mainController分配给ToolbarController AFTER ,创建工具栏视图,然后调用ToolbarView.__getatrr__,调用ToolbarController.__getattr__尝试访问self.mainController尚不存在!

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

<强>之前

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的帮助。