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

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

据我所知,这个问题已经被问过好几次了。但是在尝试了以下解决方案之后:

我得出的结论是我需要帮助解决我的具体问题。列出的解决方案似乎不适用于我的具体情况。

以下情况:

我目前正在尝试使用 kivy 为智能手机开发应用程序。因为我喜欢我的代码非常干净和清晰的结构,所以我将我的 Kivy 代码分成几个 kv 文件。 python 代码应该主要有逻辑,仅此而已。为了让它正常工作,我需要在不同的 kv 文件中引用不同对象的实例。为了弄清楚我的问题,我构建了一个相当简单的示例:

文件: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()

文件: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:

文件: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()

文件: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

运行 kivy 版本 1.10 和 Python 版本 3.7.2 程序首先启动非常好。但是,当我按下标有 "press" 且 ID 为 ERRORBUTTON 的按钮时,出现此错误:

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

尽管我缩短了错误消息,但应该清楚发生了什么。在字典中找不到我在 RequirementC Class 中引用的 ERRORBUTTON id。现在回答我的问题:

我怎样才能让它发挥作用?我缺了什么?

简述我尝试过的一些事情:

None 这些尝试似乎对我有效。

总结一下:

如何让我的 kivy 引用跨不同 类 正常工作,为什么 ERRORBUTTON id 不在我正在查看的字典中?

问题是由一个常见错误引起的,ids是相对于一个小部件的,例如在你的情况下让我们分析一下表达式:

self.ids.ERRORBUTTON

self是谁?self是RequirementC的实例。

你有什么实例?那我们看看实现RequirementC的.kv:

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

如果您注意到唯一可以访问 REQC 的 ID,那么 RequirementC 不存在 ID ERRORBUTTON。

那么ERRORBUTTON这个id是属于哪个class的?那么我们来回顾一下ERRORBUTTON是在哪里创建的:

 # ...

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

如你所见,ERRORBUTTON 是 ComplexBox 的一个 id。


通过前面的介绍,我们已经知道问题的原因了。在给出解决方案之前,我们首先了解一个编程的基本原则:class 是一个行为的抽象,它必须明确定义你想要暴露给外部(因此如果你查看任何库的文档,请不要记录所有方法或所有 classes,因为这个想法是抽象 class,也就是说,使用该库的人不想知道它在内部如何以如此精确的方式工作),所以在设计时考虑 classes 将采用什么方法是很好的。比如我们创建了一个Personclass,这个class有一些属性,比如大小或者重量,如果你觉得有必要暴露你的心脏或大脑有多重怎么办?好吧,不。你的情况也是如此。

解决方案是公开事件 on_release,使其成为 RequirementC class 的一部分,此外将 ERRORBUTTON 公开为 属性(在我的例子中,我不喜欢使用 id,因为它们降低了代码的可读性),然后在具有公共作用域的地方建立连接。

*.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')

所以经过一些研究,我找到了我正在寻找的答案。对于那些可能也会在这里遇到我的问题的人来说:

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

通过遍历演示文稿的子项,可以使用 "ids" 方法。

但对于那些现在想使用它的人,我建议遍历子项以找到正确的 ID,而不是提供 "hard" 引用 (children[1])。