Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Object-Oriented Python Dice Roller RPG Roller

Anthony Grodowski
Anthony Grodowski
4,902 Points

A big confusion in a challange

Why am I getting an output like this? It seems like cls() is removing all of the values inside list_sum. Why?

>>> from hands import Hand                                                               
>>> from dice import D20                                                                 
>>> Hand.roll(2)                                                                         
[12, 17]                                                                                 
[12, 17]                                                                                 
[]                                                                                       
[12, 17]                                                                                 
[12, 17]                                                                                 
[]                                                                                       
dice.py
import random


class Die:
    def __init__(self, sides=2):
        if sides < 2:
            raise ValueError("Can't have fewer than two sides")
        self.sides = sides
        self.value = random.randint(1, sides)

    def __int__(self):
        return self.value

    def __add__(self, other):
        return int(self) + other

    def __radd__(self, other):
        return self + other

class D20(Die):
    def __init__(self):
        super().__init__(sides=20)
hands.py
from dice import D20

class Hand(list):
    def __init__(self, list_sum=None):
        self.list_sum = list_sum
        print(self.list_sum)
        print(list_sum)

    @classmethod
    def roll(cls, times):
        list_sum = []
        for _ in range(times):
            list_sum.append(D20().value)
        print(cls(list_sum))
        return cls(list_sum)


    @property
    def total(self):
        return sum(self)

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,441 Points

By adding numbers to the print statements, you can follow the flow:

class Hand(list):
    def __init__(self, list_sum=None):
        self.list_sum = list_sum
        print(1, self.list_sum)
        print(2, list_sum)

    @classmethod
    def roll(cls, times):
        list_sum = []
        for _ in range(times):
            list_sum.append(D20().value)
        print(3, cls(list_sum))
        return cls(list_sum)

    @property
    def total(self):
        return sum(self)


a= Hand.roll(2)
print(4, a)

Produces:

1 [5, 8]
2 [5, 8]
3 []
1 [5, 8]
2 [5, 8]
4 []

The print before the return and the return both create instances of Hand The instance from the print is thrown away.

Anthony Grodowski
Anthony Grodowski
4,902 Points

Thanks Chris, I still don't understand, why cls() makes list_sum to loose it's content. Could you please explain it to me?(sorry if it's already in your answer and I can't see it)

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

Adding a few more prints:

class Hand(list):
    def __init__(self, list_sum=None):
        self.list_sum = list_sum
        print(1, self.list_sum)
        print(2, list_sum)

    @classmethod
    def roll(cls, times):
        list_sum = []
        for _ in range(times):
            list_sum.append(D20().value)
        a= cls(list_sum)
        print('3a', a)
        print('3b', a.list_sum)
        print(3, cls(list_sum))
        return cls(list_sum)

    @property
    def total(self):
        return sum(self)


a= Hand.roll(2)
print(4, a)

Produces:

1 [13, 8]
2 [13, 8]
3a []
3b [13, 8]
1 [13, 8]
2 [13, 8]
3 []
1 [13, 8]
2 [13, 8]
4 []

Printing the instance which is an extension of list produces the empty list since nothing was append to self. The custom added attribute list_sum does display as printed.

Anthony Grodowski
Anthony Grodowski
4,902 Points

Thanks Chris! I've played a bit with that code and I came to a conclusion that I completely don't need self in my code. I was struggling with understanding your answer above becasue I didn't get how is self neccessary in this code. Did you mean by saying self the cls()? Because then it would make sense. I finally undertand the point of having @classmethod - they don't need an instance to work on. Am I getting that right? Here's my code:

from dice import D20


class Hand(list):
    @classmethod
    def roll(cls, times):
        print(2)
        list_sum = cls()
        for _ in range(times):
            list_sum.append(D20())
        print(3, list_sum)
        print(4, sum(list_sum))
        return list_sum

    @property
    def total(self):
        return sum(self)


a= Hand.roll(2)
print(1, a)
print(2, a.total)

...and the output:

2                                                                                        
3 [<dice.D20 object at 0x7f20008d5080>, <dice.D20 object at 0x7f20008d50b8>]             
4 9                                                                                      
1 [<dice.D20 object at 0x7f20008d5080>, <dice.D20 object at 0x7f20008d50b8>]             
2 9   

EDIT After a minute I realised that in

 @property
    def total(self):
        return sum(self)

there's self! How is it possible that it still works even tho I removed everything with conection to the instance from the code? Is self and cls() basically the same thing? I came to that conclusion after changing self to cls in

@property
    def total(cls):
        return sum(cls)

Also I don't understand what's the difference between just cls and cls()

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

Good point! self and cls are similar in that they are both placeholders used to bind the method to an object. "Binding" means to attach the method to an instance or a class that the method will use as a context to operate within.

Both of the terms self and cls are always used as the first positional parameter. For class methods, the cls is assigned when the class is parsed. for instance methods, the self is assigned during the instance creation.

The names of the parameters self and cls are a Python convention. Changing the name of the first positional argument to something else does not change the functionality, rather it only affects the readability of the code. So changing self to cls in the total method does not affect it's functionality. In the method with self changed to cls, the parameter cls will still be assigned to point to the instance of the class.

By adding print inside the total method, you can see whether it is self or cls, the value of the parameter is the same. In fact, you could you "smith" and "agent" instead of self and cls as the name of the positional arguments.

class Hand(list):
    @classmethod
    def roll(cls, times):
        print(2, "class:", cls)
        list_sum = cls()
        for _ in range(times):
            list_sum.append(D20())
        print(3, "instance:", list_sum)
        print(4, "instance:", sum(list_sum))
        return list_sum

    @property
    def total_self(self):
        print(5, "instance:", self)
        return sum(self)

    @property
    def total_cls(cls):
        print(6, "instance:", cls)
        return sum(cls)


a = Hand.roll(2)
print(1, "instance:", a)
print(2, "instance:", a.total_self)
print(6, "instance:", a.total_cls)
$ python rpg_roller.py 
2 class: <class '__main__.Hand'>
3 instance: [<__main__.D20 object at 0x7fdb1d7f1c18>, <__main__.D20 object at 0x7fdb1d7f1cc0>]
4 instance: 15
1 instance: [<__main__.D20 object at 0x7fdb1d7f1c18>, <__main__.D20 object at 0x7fdb1d7f1cc0>]
5 instance: [<__main__.D20 object at 0x7fdb1d7f1c18>, <__main__.D20 object at 0x7fdb1d7f1cc0>]
2 instance: 15
6 instance: [<__main__.D20 object at 0x7fdb1d7f1c18>, <__main__.D20 object at 0x7fdb1d7f1cc0>]
6 instance: 15
Anthony Grodowski
Anthony Grodowski
4,902 Points

Oh alright, so as long as I'm consistent in naming these parameters, functionality isn't affected. Only readabilty is... So which way is proper? Should I in the @classmethoduse cls and in the total method use self? But then constintacy rule is enroached and to me it doesn't make sense to in one part of a code name it in a different way than in an another part...

Also while testing the code one thing suprised me: how does python know that when calling return sum(cls) we want python to return the summed value of D20() attribute .value? I mean I didn't provide an information that I want .value attribute to be summed.

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

Should I in the @classmethoduse cls and in the total method use self? Yes! Though it may look inconsistent, the point of using cls in a classmethod is to provide a visual reminder that you are operating on the class and not on an instance of the class.

How does python know that when calling return sum(self) we want python to return the summed value of D20() attribute .value? The sum() function expects an iterable. Since the self instance is derived from list, sum() will be able iterated over each item in the Hand instance and calling the item's __add__ method. In this case, it calls the D20 instance __add__ method, which returns an int.