Search code examples
pythonkivykivy-language

Kivy: AttributeError: 'super' object has no attribute '__getattr__'


Let me preface this by saying that I know many people have asked this question. But after trying the following solutions:

I still haven't been able to rectify my issue, so I've come to the conclusion that I need help with my specific problem.

Context

I am building a larger GUI for school children (hence the mathematical id names), in which I create several custom widgets. As the program is already quite large, for the purpose of this question, I've fashioned a smaller one that is much more succinct. The intended output is as follows: on the screen are two texts "Blue" and "Green" separated by a line. When the user presses the up arrow, the "Blue" text becomes a red box, and when the user presses the down arrow, the "Green" text becomes a red box.

Code

Python file:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty, ListProperty
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window


class ChildWidget(Widget):
    text = StringProperty()
    color = ListProperty()

    def change_color_and_text(self):
        self.text = 'Red'

        with self.canvas:
            self.canvas.clear()
            Color(1, 0, 0, 1)
            Rectangle(pos=(self.x, self.y), size=(self.width, self.height))


class ParentWidget(Widget):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.numerator = self.ids.numerator
        self.denominator = self.ids.denominator

    def on_key_down(self, window, key, *args):
        if key == 38:  # up
            self.numerator.change_color_and_text()
        elif key == 40:  # down
            self.denominator.change_color_and_text()


class ExampleApp(App):
    def on_start(self):
        parent = self.root.ids.parent
        Window(on_key_down=parent.on_key_down)


if __name__ == '__main__':
    ExampleApp().run()

Kivy file:

<ChildWidget>:
    Label:
        text: root.text
        color: root.color
        pos: self.x, self.y

<ParentWidget>:
    canvas:
        Color:
            rgba: 0, 0, 0, 1
        Line:
            points: [self.x, self.y + self.height/2, self.x + self.width, self.y + self.height/2]
            width: 1

    ChildWidget:
        id: numerator
        text: 'Blue'
        color: [0, 0, 1, 1]
        pos: self.x, self.y + self.height + 5
        size_hint: None, None
        size: 50, 50

    ChildWidget:
        id: denominator
        text: 'Green'
        color: [0, 1, 0, 1]
        pos: self.x, self.y - self.height - 5
        size_hint: None, None
        size: 50, 50

FloatLayout:
    ParentWidget:
        id: parent
        pos: 300, 400
        size_hint: None, None
        size: 200, 200

Problem

When I run the above I get the following error:

File "kivy/properties.pyx", line 860, in kivy.properties.ObservableDict.__getattr__
 KeyError: 'numerator'
 
During handling of the above exception, another exception occurred:
 
... [skip large chunk of immaterial traceback] ...

line 25, in __init__
     self.numerator = self.ids.numerator
   File "kivy/properties.pyx", line 863, in kivy.properties.ObservableDict.__getattr__

 AttributeError: 'super' object has no attribute '__getattr__'

Can anyone explain why I am getting this error?

Here are a few things I have tried adding geometry managers to my widgets and have tried rearranging the order in the python code, but those didn't work. I have also tried passing self.numerator = self.root.ids.numerator but got an Attribute error: AttributeError: 'ParentWidget' object has no attribute 'root'.

I just don't understand what is going wrong! The syntax looks correct, for example I haven't put quotes around my ids - which several other people asking this question did - and I think the id referencing is correct, as in the id numerator is in the widget parent so referencing self.ids.numerator seems correct. But clearly I am going wrong somewhere.

Any help on the subject is greatly appreciated.

Thanks.

p.s. I understand that doing things the way I did them in the code above is probably not the best way to approach this. For example, I didn't need to create a custom widget that essentially is a label, as a label would have done everything I specified and would have reduced complexity in my code. Just remember that this code was created to emulate a larger Python script.


Solution

  • I think the ids dict isn't yet populated at the point you try to access it.

    Instead it's easiest to do this via kv, use numerator: numerator in the ParentWidget rule, and declare numerator = ObjectProperty() in the Python code. Do the same for denominator.