Kivy:在 kv 文件中定义 属性 的 AttributeError
Kivy: AttributeError with property defined in kv file
- Python: 3.6.8
- 基维: 1.9.0
- PyCharm: 2019.2.3
我正在阅读“Kivy – Python 第二版 中的交互式应用程序和游戏”,同时编写和测试书中的源代码。
当我完成第 3 章时,我遇到了这个错误:
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'
我尝试通过将我的代码与从书上下载的源代码进行比较来定位错误,但没有发现相关差异。在几乎相同的两个版本中,我不认为 status_bar 和 DrawingSpace.
之间的关系有任何差异
我在下面粘贴我的代码。我忽略了这些文件:
工具箱。*
漫画小部件。*
常规选项。*
因为我觉得他们不相关,所以一切都比较容易。但是如果有人问或者这个问题没有得到解决,我会按需粘贴它们。
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"
如您所见,在 kv 中定义属性会在依赖它们时引入解析顺序问题。最好的解决方案可能是只对简单的事物使用动态创建的属性,否则只需在 class 定义中正常定义属性。
我怀疑正在发生的事情是 DrawingSpace
的 on_children()
方法在设置 status_bar
属性 之前被调用。由于每当 DrawingSpace
的 children
更改时都会调用 on_children()
方法,因此您可以通过添加检查是否已设置来保护对 status_bar
的引用:
class DrawingSpace(RelativeLayout):
def on_children(self, instance, value):
if self.status_bar is not None:
self.status_bar.counter = len(self.children)
至于为什么你的代码需要这个而你书中的代码不需要 - 我无法猜测,因为我没有那本书。
我正在关注同一本书并且遇到了同样的问题!阅读这些回复后,@JohnAnderson 是正确的 - DrawingSpace.on_children()
在 status_bar
存在于 DrawingSpace
之前被调用。但为什么?
请记住,此处应用程序的根小部件是 ComicCreator
,如 comiccreator.kv
中所定义。如果您看一下 DrawingSpace
的实例,status_bar
是在它自己的 id
之后定义的,所以看起来应该没有问题。
但是,我们必须记住 kv class 规则 首先执行。所以查看 drawingspace.kv
,我们看到 <DrawingSpace>
规则导入 python class(因此它已经知道 on_children
方法)然后添加一个 StickMan
在规则内。 DrawingSpace
的实例在 comiccreator.kv
文件中添加了一个 status_bar
属性之前发生的所有事情。
只需从drawingspace.kv
中删除StickMan
,错误就会消失。 [编辑 以下是不正确的:如果需要,您可以在 comiccreator.kv
中添加 StickMan
作为 DrawingSpace
的子项(在 status_bar
已添加),你将在视觉上得到相同的结果,没有错误。]
最后,您应该从 <DrawingSpace@RelativeLayout>
中删除 @RelativeLayout
。在 python 中定义自定义 class 并从基础 class 继承后,您不再需要使用 @
从 kv 中的基础 class 继承操作员。有关详细信息,请参阅第 10 页的注释。
- Python: 3.6.8
- 基维: 1.9.0
- PyCharm: 2019.2.3
我正在阅读“Kivy – Python 第二版 中的交互式应用程序和游戏”,同时编写和测试书中的源代码。
当我完成第 3 章时,我遇到了这个错误:
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'
我尝试通过将我的代码与从书上下载的源代码进行比较来定位错误,但没有发现相关差异。在几乎相同的两个版本中,我不认为 status_bar 和 DrawingSpace.
之间的关系有任何差异我在下面粘贴我的代码。我忽略了这些文件:
工具箱。*
漫画小部件。*
常规选项。*
因为我觉得他们不相关,所以一切都比较容易。但是如果有人问或者这个问题没有得到解决,我会按需粘贴它们。
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"
如您所见,在 kv 中定义属性会在依赖它们时引入解析顺序问题。最好的解决方案可能是只对简单的事物使用动态创建的属性,否则只需在 class 定义中正常定义属性。
我怀疑正在发生的事情是 DrawingSpace
的 on_children()
方法在设置 status_bar
属性 之前被调用。由于每当 DrawingSpace
的 children
更改时都会调用 on_children()
方法,因此您可以通过添加检查是否已设置来保护对 status_bar
的引用:
class DrawingSpace(RelativeLayout):
def on_children(self, instance, value):
if self.status_bar is not None:
self.status_bar.counter = len(self.children)
至于为什么你的代码需要这个而你书中的代码不需要 - 我无法猜测,因为我没有那本书。
我正在关注同一本书并且遇到了同样的问题!阅读这些回复后,@JohnAnderson 是正确的 - DrawingSpace.on_children()
在 status_bar
存在于 DrawingSpace
之前被调用。但为什么?
请记住,此处应用程序的根小部件是 ComicCreator
,如 comiccreator.kv
中所定义。如果您看一下 DrawingSpace
的实例,status_bar
是在它自己的 id
之后定义的,所以看起来应该没有问题。
但是,我们必须记住 kv class 规则 首先执行。所以查看 drawingspace.kv
,我们看到 <DrawingSpace>
规则导入 python class(因此它已经知道 on_children
方法)然后添加一个 StickMan
在规则内。 DrawingSpace
的实例在 comiccreator.kv
文件中添加了一个 status_bar
属性之前发生的所有事情。
只需从drawingspace.kv
中删除StickMan
,错误就会消失。 [编辑 以下是不正确的:如果需要,您可以在 comiccreator.kv
中添加 StickMan
作为 DrawingSpace
的子项(在 status_bar
已添加),你将在视觉上得到相同的结果,没有错误。]
最后,您应该从 <DrawingSpace@RelativeLayout>
中删除 @RelativeLayout
。在 python 中定义自定义 class 并从基础 class 继承后,您不再需要使用 @
从 kv 中的基础 class 继承操作员。有关详细信息,请参阅第 10 页的注释。