Gilgamesch Gilgamesch - 7 days ago 6
Python Question

kivy NumericProperty to StringProperty

I tried to make my own Coockie Clicker in kivy, but with cristmas coockies.
I created an Image of an Coockie, you can click on and an Label that shows you how many times you have clicked.
The Label required an String, so I tried to convert the numeric property into an string, but it did not work, because I got the error message:

<kivy.properties.NumericProperty object at 0xa6e32cc>


This is the part of the code, where I suspect the error:

class Kecks(Widget):

count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count))


Here is the rest of the code:

from kivy.app import App
from kivy.lang import Builder

from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.core.window import Window

from kivy.clock import Clock
from kivy.animation import Animation
from kivy.core.text.markup import *

from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty
from kivy.properties import StringProperty

Builder.load_string('''
<Root>:

Kecks:
pos: 300, 300
<Kecks>:
Image:
pos: root.pos
id: my_image
source: 'piernik.png'

Label:
id: my_Label
font_size: 50
text: root.txt
center_x: root.width / 4

''')

class Root(FloatLayout):
pass

class Kecks(Widget):

count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count))

def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.count += self.amount
print self.txt


class app(App):
def build(self):
Window.clearcolor = (10, 0, 0, 1)
return Root()

if __name__ == "__main__":
app().run()

Answer

That's not an error message, that's a string identifying a property instance. Do you really mean you got an error, or is that what's printed by your program? I guess the latter, because...

count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count)) 

To understand this, you need to know a little about how properties work - you declare them at the class level, not within a method, so they are attributes of the class (which are inherited as attributes of individual instances). One effect of this is that all instances of the class share the same property objects.

When you access them within a method, e.g. with self.count, you get an instance-specific result that behaves like a normal non-property attribute, even though it's really a property object, because the property internally takes care of returning the right thing (I think it's right to say it's a descriptor).

What's happening here is that you're accessing the result at class level, when it doesn't have any special behaviour - you asked for str(count) and that's what you got, the string identifying the property object.

Probably the correct pythonic way to resolve this is

class Kecks(Widget):
    count = NumericProperty(0)
    amount = NumericProperty(1)
    txt = StringProperty()

    def __init__(self, *args, **kwargs):
        super(Kecks, self).__init__(*args, **kwargs):
        self.txt = str(self.count)

By setting the value in __init__, you get the correct instance-level behaviour. You can also do stuff like mess around with accessing count.defaultvalue to get its actual value, but this is probably a bad idea.

If you want txt to be bound to the value of count (automatically changing to track str(count)), you have to do more again, but I'm not clear if that's your intention so I won't go into it and you can check the doc anyway.