1

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.

Community
  • 1
  • 1
zapatilla
  • 1,711
  • 3
  • 22
  • 38
  • it has nothing to do with your main controller. i want to see `actionPerformed=getattr(self, method)`, what is `self.method`? for instance, why you need to use `getattr` instead of `.` operator? – Jason Hu Sep 30 '15 at 15:15
  • 1
    Two things I wonder. #1 - Why haven't you specified a base class for your `MainController` and `ToolbarController`? I haven't done OOP in Jython at all, only functional programming, but my understanding was that Jython was stuck at version 2, which means that your objects should inherit from `object`, not nothing, if you want to rely on new style class behavior (which I think `getattr` is part of). #2 - Are your classes really what you think they are? Debug - in `ToolbarController.__getattr__`, print out the class of `self.mainController`. I'm guessing it's a `ToolbarController` instance. – ArtOfWarfare Sep 30 '15 at 15:40
  • Thanks for the tips, @ArtOfWarfare. #1 Jython is still in version 2.X. I've changed now my classes and they inherit from object. I'll edit my question with that as well. #2 Interesting. I get the stack overflow error when trying to access `self.mainController.__class__.__name__`, so it's not getattr that is causing trouble, it seems it's the reference to the mainController :O – zapatilla Sep 30 '15 at 16:10
  • I'm stupid! I was assigning the `mainController` to the `ToolbarController` **AFTER** creating the `ToolbarView` which then calls `ToolbarView.__getatrr__`, which calls `ToolbarController.__getattr__` which tries to access `self.mainController` which doesn't exist yet! – zapatilla Sep 30 '15 at 16:22
  • 1
    Oh man, posting this question on this site <3 – Alvaro Sep 30 '15 at 16:33
  • I knew I'd get this kind of comments :) It needed to be done! – zapatilla Sep 30 '15 at 16:34
  • @zapatilla aha! you got it. that makes sense. and my answer will apply to that scenario too, since `self.mainController` doesn't exist and `__getattr__` will be invoked too. – Jason Hu Sep 30 '15 at 16:35

2 Answers2

1

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.

Jason Hu
  • 6,239
  • 1
  • 20
  • 41
  • Thanks a lot for your reply, @HuStmpHrrr. I've edited the question to show I also have methods for OpenFile, SaveFile and CloseFile. Copy-pasting from the code not to make the post too long was little messy :) So from your reply, you say the SO is caused by the MainController not having defined the method I'm calling trying to delegate from the ToolbarController and ToolbarView, right? This inspired me to add a `__getattr__ ` method definition like the one in your example and check the name of the method that I'm trying to execute but doesn't exist. The result is – zapatilla Sep 30 '15 at 15:41
  • ...the code never gets to the MainController, and the SO is still happening. Also, I don't quite understand the use of super in the actionPerformed of the JButton. Isn't ToolbarView's super JToolbar? JToolbar doesn't seem to have a `__getattr__` method: `button = JButton(icon, actionPerformed=super(ToolbarView, self).__getattr__("onNewFileClick"))` `AttributeError: 'super' object has no attribute '__getattr__'` ` – zapatilla Sep 30 '15 at 15:42
  • @zapatilla stuck at the same place? that's bazaar. i will look closer to see if i missed something. – Jason Hu Sep 30 '15 at 15:44
  • 1
    @zapatilla ok. this exception happens because your are using jython and dealing with java classes, which of course has no predefined hooks method like `__getattr__`. i will check – Jason Hu Sep 30 '15 at 15:47
  • Aaah, that makes sense! Jython is causing me a lot of headaches :) – zapatilla Sep 30 '15 at 15:49
  • 1
    @zapatilla try `super(ToolbarView, self).__getattribute__("onNewFileClick")` – Jason Hu Sep 30 '15 at 15:51
  • Interesting @HuStmpHrrr. I get this `AttributeError: 'ToolbarView' object has no attribute 'onNewFileClick'` which is true because ToolbarView doesn't have that method, but I was expecting it would trigger `ToolbarView.__getattr__`. This is a bit confusing. – zapatilla Sep 30 '15 at 16:00
  • Oh, hold on, I hadn't noticed `__getattribute__` is not `__getattr__`! My mistake. – zapatilla Sep 30 '15 at 16:03
  • Thanks for your help, @HuStmpHrrr. I finally found the problem. It wasn't the `getattr`! See my comment above http://stackoverflow.com/questions/32869430/stackoverflowerror-when-using-getattr-in-jython#comment53573959_32869430 – zapatilla Sep 30 '15 at 16:24
1

I was blaming this to getattr since I'm not that confident using it yet, but it turns out it was something rather basic.

I was assigning the mainController to the ToolbarController AFTER creating the ToolbarView which then calls ToolbarView.__getatrr__, which calls ToolbarController.__getattr__ which tries to access self.mainController which doesn't exist yet!

This is the change I needed to make in the ToolbarController class.

Before

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)

After:

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)

Thanks a lot to @HuStmpHrrr and @ArtOfWarfare for their help.

zapatilla
  • 1,711
  • 3
  • 22
  • 38