简体   繁体   中英

Kivy: AttributeError with property defined in kv file

  • Python : 3.6.8
  • Kivy : 1.9.0
  • PyCharm : 2019.2.3

I'm reading " Kivy – Interactive Applications and Games in Python Second Edition " while writing and testing the source code in the book.

When I was finishing chapter 3 I got to this error:

Exception ignored in: 'kivy.properties.observable_list_dispatch'
Traceback (most recent call last):
File "kivy/properties.pyx", line 579, in kivy.properties.Property.dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/properties.c:7216)
File "kivy/_event.pyx", line 1214, in kivy._event.EventObservers.dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/_event.c:14036)
File "kivy/_event.pyx", line 1120, in kivy._event.EventObservers._dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/_event.c:13194)
File "/home/madtyn/PycharmProjects/learning_kivy/comics/drawingspace.py", line 8, in on_children
    self.status_bar.counter = len(self.children)
AttributeError: 'DrawingSpace' object has no attribute 'status_bar'

I tried to locate the error by comparing my code with the downloaded source code from the book, but I found no relevant difference. In both versions which are almost identical, I don't appreciate any difference in the relation between status_bar and DrawingSpace.

I'm pasting my code below. I'm omitting these files:

  • toolbox.*

  • comicwidgets.*

  • generaloptions.*

    because I think they are not relevant, so everything it's easier. But I will paste them on demand if anyone asks or this doesn't get resolved.

comiccreator.py

import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.anchorlayout import AnchorLayout

kivy.require('1.9.0')

Builder.load_file('toolbox.kv')
Builder.load_file('drawingspace.kv')
Builder.load_file('comicwidgets.kv')
Builder.load_file('generaloptions.kv')
Builder.load_file('statusbar.kv')


class ComicCreator(AnchorLayout):
    pass


class ComicCreatorApp(App):
    def build(self):
        return ComicCreator()


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

comiccreator.kv

# File name: comiccreator.kv
#:kivy 1.9.0

<ComicCreator>:
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        ToolBox:
            id: _tool_box
            drawing_space: _drawing_space
            comic_creator: root
            size_hint: None, None
            width: 100
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        DrawingSpace:
            id: _drawing_space
            status_bar: _status_bar  # <====== Here we define the status_bar property!!!
            general_options: _general_options
            tool_box: _tool_box
            size_hint: None, None
            width: root.width - _tool_box.width
            height: root.height - _general_options.height - _status_bar.height
    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'bottom'
        BoxLayout:
            orientation: 'vertical'
            GeneralOptions:
                id: _general_options
                drawing_space: _drawing_space
                comic_creator: root
                size_hint: 1,None
                height: 48
            StatusBar:
                id: _status_bar
                size_hint: 1,None
                height: 24

drawingspace.py

# File name: drawingspace.py
from kivy.properties import ObjectProperty
from kivy.uix.relativelayout import RelativeLayout


class DrawingSpace(RelativeLayout):
    def on_children(self, instance, value):
        self.status_bar.counter = len(self.children)  # Here the error states that
                                                      # status_bar attr does not exist

drawingspace.kv

# File name: drawingspace.kv

#:kivy 1.9.0
#:import drawingspace drawingspace

<DrawingSpace@RelativeLayout>:
    canvas.before:
        Line:
            rectangle: 0, 0, self.width - 4, self.height - 4
    StickMan:

statusbar.py

# File name: statusbar.py
import kivy
kivy.require('1.9.0')

from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ObjectProperty


class StatusBar(BoxLayout):
    counter = NumericProperty(0)
    previous_counter = 0

    def on_counter(self, instance, value):
        if value == 0:
            self.msg_label.text = "Drawing space cleared"
        elif value - 1 == self.__class__.previous_counter:
            self.msg_label.text = "Widget added"
        elif value + 1 == StatusBar.previous_counter:
            self.msg_label.text = "Widget removed"
        self.__class__.previous_counter = value

statusbar.kv

# File name: statusbar.kv
#:kivy 1.9.0
#:import statusbar statusbar

<StatusBar@BoxLayout>:
    msg_label: _msg_label
    orientation: 'horizontal'
    Label:
        text: 'Total Figures: ' + str(root.counter)
    Label:
        id: _msg_label
        text: "Kivy started"

I suspect what is happening is that your on_children() method of DrawingSpace is being called before the status_bar property is set. Since the on_children() method is called whenever the children of DrawingSpace changes, you can protect the reference to status_bar by adding a check of whether it is set:

class DrawingSpace(RelativeLayout):
    def on_children(self, instance, value):
        if self.status_bar is not None:
            self.status_bar.counter = len(self.children)

As to why your code needs this and the code in your book doesn't - I cannot guess, since I don't have that book.

Defining properties in kv can introduce parse order issues when things depend on them, as you've found. The best solution is probably to only use dynamically created properties for simple things, and otherwise just define the properties normally in the class definition.

I am following the same book and had the same issue, After reading these responses, @JohnAnderson is correct - DrawingSpace.on_children() gets called before status_bar exists within the DrawingSpace . But why?

Remember that the root widget of the app here is a ComicCreator as defined in comiccreator.kv . If you take a look at the instance of DrawingSpace there, status_bar is defined right after its own id , so it would seem that there shouldn't be a problem.

But, we must remember that kv class rules are executed first. So looking at drawingspace.kv , we see that the <DrawingSpace> rule imports the python class (so it already knows about the on_children method) and then adds a StickMan within the rule . All that happens BEFORE the INSTANCE of DrawingSpace gets a status_bar attribute added to it within the comiccreator.kv file.

Simply delete the StickMan from drawingspace.kv and the error will disappear. [ Edit the following is incorrect: If you want, you can add StickMan as a child of DrawingSpace in comiccreator.kv (after status_bar has been added) and you will visually get the same result with no error.]

Lastly, you should remove @RelativeLayout from <DrawingSpace@RelativeLayout> . Once you define a custom class in python and inherit from a base class, you no longer need to inherit from a base class in kv using the @ operator. See the notes on page 10 for more info on that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM