【问题标题】:AttributeError: 'super' object has no attribute '__getattr__' Error when using BoxLayout with several kv-files in KivyAttributeError: 'super' object has no attribute '__getattr__' 在 Kivy 中使用带有多个 kv 文件的 BoxLayout 时出错
【发布时间】:2019-03-15 23:27:55
【问题描述】:

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

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

以下情况:

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

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

文件:尝试.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 版运行,该程序首先可以正常启动。但是,当我按下标有“按下”且 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 类中引用的 ERRORBUTTON id。现在回答我的问题:

我怎样才能让它工作?我缺少什么?

简而言之,我尝试了几件事:

  • 我已尝试将 BoxLayouts 包装在 Screen 中并通过 screenmanager 访问它们。
  • 我已经尝试在 python 代码中重新排列顺序。 (例如先加载主kv文件)
  • 我已尝试使用 Builder Factory 并在那里注册不同的类。
  • 我已尝试更改参考。 (例如 self.ids.['ERRORBUTTON']...)

这些尝试对我来说似乎都没有奏效。

总结一下:

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

【问题讨论】:

    标签: python kivy kivy-language


    【解决方案1】:

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

    self.ids.ERRORBUTTON
    

    self是谁?self是RequirementC的实例。

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

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

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

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

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

    如您所见,ERRORBUTTON 是 ComplexBox 的 id。


    通过上一部分提到的,我们已经知道了问题的原因。在给出解决方案之前,我们首先了解编程的基本原则:类是一种行为的抽象,它必须清楚地定义你想要暴露给外部(因此,如果你查看任何库的文档,请不要记录所有方法或所有类,因为这个想法是抽象类,也就是说,使用该库的人不想知道它在内部是如何以如此精确的方式工作的),因此最好设计思考类将具有哪些方法.比如说我们创建了一个Person类,这个类有一定的大小或者重量等属性,如果你觉得有必要暴露你的心脏或者大脑的重量怎么办?嗯,不。你的情况也是如此。

    解决方案是公开事件 on_release,使其成为 RequirementC 类的一部分,此外还公开 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')
    

    【讨论】:

    • 感谢您的快速答复。就我而言,我的应用程序可能连接到数据库。这就是为什么我需要从 python 代码中通过 id 访问按钮和标签的原因。我希望能够动态添加新的小部件,给它们一个 id,然后以与 python 程序不同的方式操作它们。 kv 文件应该描绘我在不同点插入的或多或少的空模板。如果我理解正确,on_release 操作是硬编码在 kv 文件中的。有没有办法避免这种情况?
    • @FranzDahmann ids不能也不应该动态添加,这使得应用程序无法忍受,而是为每个功能创建一个类:单一责任原则en.wikipedia.org/wiki/Single_responsibility_principle,所以我建议在编写代码之前你设计你的课程。正如所指出的,这些类不应该过多地依赖其他类,应该使用它们,但不要像你的情况那样依赖。
    • @FranzDahmann 如果您详细解释您到底想要做什么,我可以给您一个更好的解决方案,也就是说,每个类都有什么功能。我建议改变你对类的看法,一个类就像一个黑盒子,它只暴露某些属性并具有修改类状态的功能,其余的都是隐藏的。要驾驶汽车,您必须知道电动机是如何制造的?不,你只需要知道如何开车。
    • 好的,谢谢您的建议。让我尝试更详细地了解它:我在单独的 kv 文件中有 Widget RequirementC。这导致它在 python 代码中有一个单独的对应类。此小部件中的 Button 应该更改 ComplexBox 中 ID 为 ERRORBUTTON 的 Button 的文本。 ComplexBox 在 python 的不同类中定义。现在这是棘手的部分......应该在 ERRORBUTTON 中显示的文本将在 python 代码中生成。在这种情况下,在 kv 文件中进行硬编码将无济于事。
    • 当我按下 REQC 按钮时,我需要找到一种方法将在 python 中生成的文本带到 kv 文件中的 ERRORBUTTON 中。
    【解决方案2】:

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

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

    通过查看演示文稿的子项,可以使用“ids”方法。

    但对于那些想现在使用它的人,我建议遍历孩子以找到正确的 id,而不是给出“硬”参考(孩子 [1])。

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 1970-01-01
      • 2022-10-13
      • 2023-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-20
      相关资源
      最近更新 更多