AttributeError: 'super' object has no attribute '__getattr__' Error when using BoxLayout with several kv-files in Kivy

不羁的心 提交于 2021-01-27 11:32:16

问题


As I am very well aware, this question has been asked several times already. But after trying the following solutions:

  • Python - Kivy: AttributeError: 'super' object has no attribute '__getattr__' when trying to get self.ids
  • Python/Kivy AttributeError: 'super' object has no attribute '__getattr__'
  • How can i access object properties of another class in kivy without getting AttributeError: 'super' object has no attribute '__getattr__'
  • AttributeError: 'super' object has no attribute '__getattr__'

I have reached the conclusion that i need help in my specific problem. The solutions listed didn't seem to work in my specific case.

Following situation:

I am currently trying to develop an application for smartphones using kivy. Since i like my code pretty clean and clear structured i have split my Kivy code into several kv-files. The python code is supposed to have primarily the logic and nothing more. In order to get it working properly i need to reference the instances of the different objects in the different kv-files. In order to make my problem clear i have constructed a fairly simple example:

FILE: attempt.py

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.factory import Factory
from kivy.uix.label import Label
from kivy.lang import Builder

x= 1

class ComplexBox(Widget):
    def testit(self):
        self.ids.layout.add_widget(Label(text = "Requirement A met."))
    def addsome(self):
        global x
        self.ids.layout.add_widget(SomeWidget(id="XXX"+str(x)))
        x = x +1
    pass

class SomeWidget(Widget):
    def change(self):
        self.ids.REQB.text = "Requirement B met."
    pass

class RequirementC(Widget):
    def triggerC(self):
        self.ids.ERRORBUTTON.text = "Requirement C met"
    pass

class Attempt(App):
    def build(self):
        return presentation
    pass


presentation = Builder.load_file("attempt.kv")
Attempt().run()

FILE: attempt.kv

#:kivy 1.0
#:include attemptsupp.kv
#:include attemptsuppC.kv

# root
<ComplexBox>:
    BoxLayout:
        id: layout
        size: root.size
        Button:
            id: ERRORBUTTON
            text: "add"
            on_press: root.addsome()
            on_release: root.testit()
BoxLayout:
    orientation: 'vertical'
    ComplexBox:
    RequirementC:

FILE: attemptsupp.kv

#:kivy 1.0

# rules for the widget
<SomeWidget>:
    BoxLayout:
        pos: root.pos
        size: root.size
        orientation: "vertical"
        Label:
            id: REQB
            text: "hello"
        Button:
            text: "world"
            on_release: root.change()

FILE: attemptsuppC.kv

#:kivy 1.0

<RequirementC>:
    Button:
        id: REQC
        text: "Press"
        on_release: root.triggerC()

picture of the Running Program - press the "Press"- button to get the error

Running with kivy version 1.10 and Python version 3.7.2 the program first starts perfectly fine. But when I press on the Button labeled "press" with the id ERRORBUTTON I get this error:

...--default --nodebug --client --host localhost --port 57777...\attempt.py "
[INFO   ] [Logger      ] Record log in...\.kivy\logs\kivy_19-03-15_31.txt
[INFO   ] [Kivy        ] v1.10.1
[INFO   ] [Python      ] v3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 
...
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[WARNING] [Lang        ] attemptsupp.kv has already been included!
[WARNING] [Lang        ] attemptsuppC.kv has already been included!
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
[INFO   ] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "kivy\properties.pyx", line 838, in kivy.properties.ObservableDict.__getattr__
 KeyError: 'ERRORBUTTON'

 During handling of the above exception, another exception occurred:

 Traceback (most recent call last):
   File "...\ptvsd_launcher.py", line 45, in <module>
     main(ptvsdArgs)
   ...
   File "e:\Daten\Github_Projects\pc-clicker\attempt.py", line 35, in <module>
     Attempt().run()
   File "...\lib\site-packages\kivy\app.py", line 826, in run
     runTouchApp()
...
   File ...\lib\site-packages\kivy\lang\builder.py", line 64, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File ...\attemptsuppC.kv", line 7, in <module>
     on_release: root.triggerC()
   File "...\attempt.py", line 25, in triggerC
     self.ids.ERRORBUTTON.text = "Requirement C met"
   File "kivy\properties.pyx", line 841, in kivy.properties.ObservableDict.__getattr__
 AttributeError: 'super' object has no attribute '__getattr__'

Even though I shortened the error message it should be clear what happens. The ERRORBUTTON id I am referencing in the RequirementC Class can't be found in the dictionary. Now to my question:

How can i make it work? What am I a missing?

Here in short a few things i have tried:

  • I have tried wrapping the BoxLayouts in Screen and accessing them over the screenmanager.
  • I have tried rearranging the order in the python code. (for example the loading of the main kv file first)
  • I have tried using the Builder Factory and registering the different Classes there.
  • I have tried changing the references. (For example self.ids.['ERRORBUTTON']...)

None of these attempts seem to have worked in my case.

So to sum it all up:

How can I get my kivy References across different classes to work properly and why is the ERRORBUTTON id not in the dict I am looking into?


回答1:


The problem is caused by a common error, the ids are relative to a widget, for example in your case let's analyze the expression:

self.ids.ERRORBUTTON

Who is self? self is the instance of RequirementC.

what instance do you have? then let's see the .kv where RequirementC is implemented:

<RequirementC>:
    Button:
        id: REQC
        text: "Press"
        on_release: root.triggerC()

If you notice the only id that has access is to REQC, so the id ERRORBUTTON does not exist for RequirementC.

So which class does the id ERRORBUTTON belong to? So let's review where ERRORBUTTON was created:

 # ...

<ComplexBox>:
    BoxLayout:
        id: layout
        size: root.size
        Button:
            id: ERRORBUTTON
            text: "add"
            on_press: root.addsome()
            on_release: root.testit()
 # ...

As you can see ERRORBUTTON is a id of ComplexBox.


With what was mentioned in the previous part, we already know the cause of the problem. Before giving a solution we first understand a basic principle of programming: A class is an abstraction of a behavior, it must clearly define that you want to expose to the outside (therefore if you check the docs of any library do not document all the methods or all the classes since the idea is to abstract the class, that is to say, that whoever uses that library does not want to know how it works internally with such precision), so it is good to design thinking what methods the classes will have. For example let's say that we create a Person class, this class has certain attributes such as size or weight, what if you think it is necessary to expose how much your heart or brain weighs? Well, no. The same happens in your case.

The solution is to expose the event on_release so that it is part of the RequirementC class, in addition to expose ERRORBUTTON as a property (in my case I do not like to use the ids since they make the code less readable) and then make the connection in a place with common scope.

*.py

# ...

class RequirementC(Widget):
    def __init__(self, **kwargs):
        self.register_event_type('on_release')
        super().__init__(**kwargs)

    def on_release(self):
        pass

# ...

attempt.kv

#:kivy 1.0
#:include attemptsupp.kv
#:include attemptsuppC.kv

# root
<ComplexBox>:
    error_button: ERRORBUTTON # <---
    BoxLayout:
        id: layout
        size: root.size
        Button:
            id: ERRORBUTTON
            text: "add"
            on_press: root.addsome()
            on_release: root.testit()
BoxLayout:
    orientation: 'vertical'
    ComplexBox:
        id: complex_box
    RequirementC:
        on_release: complex_box.error_button.text = "Requirement C met"

attemptsuppC.kv

#:kivy 1.0

<RequirementC>:
    Button:
        id: REQC
        text: "Press"
        on_release: root.dispatch('on_release')



回答2:


So after a bit of research i found the answer i was looking for. For those who might also stumble over my problem here it comes:

class RequirementC(Widget):
def triggerC(self):
    presentation.children[1].ids.ERRORBUTTON.text = "Requirement C met"
pass

By going over the children of the presentation it is possible to use the "ids" method.

But for those tempted to use it now i would recommend iterating over the children to find the correct id instead of giving a "hard" reference (children[1]).



来源:https://stackoverflow.com/questions/55191901/attributeerror-super-object-has-no-attribute-getattr-error-when-using-b

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!