Python/kivy - TypeError: 'kivy.properties.ObjectProperty' object is not iterable

Viewed 873

So I've been, making a dice roller for my project that I call: The dungeon master's assistant for helping me DM but now let's talk about my problem.

When I press the button RollDice which is supposed to take two text inputs (Type and number of dice) and roll dice and then right now print the number and type of dice and the result in the console. However, this crashes the program instead.

This the relevant python code(can show more if needed):

class DiceRoller(Screen):

    def rolldice(self):
        self.diceNumber = ObjectProperty()
        self.diceType = ObjectProperty()


        print(F"{self.diceNumber}D{self.diceType}")


        for x in self.diceNumber:
            x = x + 1

            ActualDie = random.randint(1, self.diceType)
            Total = Total + ActualDie

            print(Total)

Here is the button that calls the function above:

Button:
    id: RollDice
    text: "Roll dice"
    pos_hint: {"top":0.3,"x":0}
    size_hint: 0.4, 0.2
    background_color: 0,0,0,0.1
    color: 0,0,0,1
        on_release:
            root.rolldice()

As I said and showed what is supposed to happen is that it's supposed to print for example 6D6 and then print the total of those dice rolls. However, this is what happens(error code):

enter image description here

The value is set by two text input boxes on the same screen: link in the kv file.

Screen - ObjectProperty links to TextInput

2 Answers

You're using properties wrong, they must be declared at class level:

class DiceRoller(Screen):
    diceNumber = ObjectProperty()
    diceType = ObjectProperty()

    def rolldice(self):
        print(F"{self.diceNumber}D{self.diceType}")
        # etc.

You'll also need to set them to an actual value before accessing them the way you did, but I assume that's covered by the code you didn't show.

Problem - ObjectProperty @ method level

With reference to your original code provided i.e. ObjectProperty declared at method level.

Root Cause

The reason is that when you link the TextInput widgets to properties in the kv file, Kivy will look for those properties in the associated class/widget. If the class/widget doesn’t have a property with the given name, an ObjectProperty will be automatically created and added to the class/widget.

The link was broken when the app re-initialized the two automatically created class attributes of ObjectProperty type in method, rolldice(), and assigned default value of None.

Solution

  • Reference the text attribute of TextInput object, and convert string to integer, int(self.diceNumber.text) and int(self.diceType.text)
  • Add range() function to the for loop
  • Declare the ObjectProperty at class level (this is optional)

Snippets - Example

class DiceRoller(Screen):
    diceNumber = ObjectProperty(None)
    diceType = ObjectProperty(None)

    def rolldice(self):
        Total = 0    # initialize Total
        print(F"{self.diceNumber.text}, D{self.diceType.text}")

        for x in range(int(self.diceNumber.text)):

            ActualDie = random.randint(1, int(self.diceType.text))
            Total += ActualDie

        print(Total)

Example

The following example illustrates that Kivy automatically created the two class attributes, diceNumber and diceType of ObjectProperty type.

main.py

import random
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder

Builder.load_string("""
<CustomLabel@Label>:    # dynamic class
    color: 0, 0, 0,1    # black color text
    text_size: self.size
    halign: 'left'
    valign: 'middle'

<DiceRoller>:
    diceNumber: diceNumber
    diceType: diceType

    canvas.before:
        Color:
            rgba: 1, 1, 1, 1    # white
        Rectangle:
            size: self.size
            pos: self.pos

    BoxLayout:
        orientation: 'vertical'

        GridLayout:
            cols: 2
            row_force_default: True
            row_default_height: 40

            CustomLabel:
                text: 'How many sides do you want your dice to have (6 for D6, 20 for D20, etc)?'
            TextInput:
                id: diceNumber
                size_hint_x: None
                width: 100

            CustomLabel:
                text: 'How many dice do you want to roll?'
            TextInput:
                id: diceType
                size_hint_x: None
                width: 100

        Button:
            id: RollDice
            text: "Roll dice"
            pos_hint: {"top":0.3,"x":0}
            size_hint: 0.4, 0.2
            background_color: 0,0,0,0.1
            color: 0,0,0,1
            on_release:
                root.rolldice()
""")


class DiceRoller(Screen):

    def rolldice(self):
        Total = 0
        print(F"{self.diceNumber.text}, D{self.diceType.text}")

        for x in range(int(self.diceNumber.text)):
            # x = x + 1

            ActualDie = random.randint(1, int(self.diceType.text))
            Total += ActualDie

        print(Total)


class TestApp(App):

    def build(self):
        return DiceRoller()


if __name__ == "__main__":
    TestApp().run()
Related