Search code examples
pythonmodel-view-controllerjbuttonjythongetattr

StackOverflowError when using getattr in Jython


I'm writing a text editor in Jython. This text editor has a toolbar which is displayed with a ToolbarView class and handled by a ToolbarController class. Some actions can't be dealt with by the ToolbarController on its own, so these are delegated to the MainController class.

To avoid repeating code since there are many actions delegated from the ToolbarController to the MainController, I've used getattr as suggested in a previous question I asked here. I have also realised I can use the same mechanism in the ToolbarView code for the actions of the buttons, but I can't get it to work and I end up getting an infinite loop and a Java StackOverflowError.

This is an extract of the relevant code:

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

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)

MainController 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...")

So what I expect is when I click the button, MainController.onNewFileClick gets executed and prints out that message in console. It works if I want to delegate from the ToolbarView to the ToolbarController, but it doesn't work when I pass that delegation from the ToolbarController to the MainController. It seems to call itself on an infinite loop. The error I get is:

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)

What am I doing wrong? I've tried something similar in python (delegating from a class to another class to another class) and it works if a put () after the getattr, but here I get confused because of the actionPerformed in the JButton. I've tried it but results are the same.


Solution

  • it seems you are using Jython, which i don't really know. anyways, in python, you override __getattr__, then you should expect getattr to use your overridden hook instead. so i think you really mean:

    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)
    

    watch how buttons are created.

    in terms of why you have a SO problem, it is because how getattr is handled. if you override __getattr__, this hook will only get called if you try to reference to a undefined field:

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

    hope it's clear how the hook work now.

    so the SO is actually caused by you were referencing something that does not belong to MainController.

    in your MainController, only onNewFileClick is defined, but you defined 3 other options:

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

    so, this will happen at the second round of iteration. since MainController has no onOpenFileClick, an AttributeError will be raised, but captured by ToolbarController, and therefore the overridden __getattr__ is invoked and on and on. that's why your call stack explodes.