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.
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.