Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python Object-Oriented Python Dice Roller Comparing and Combining Dice

Akshaan Mazumdar
Akshaan Mazumdar
3,245 Points

In the last magic method - radd- Kenneth says something which has me confused!! >>> int(self) + other VS self + other

In the r_add function we do int(self) + other , which I understand will add self.value and other

BUT ! Kenneth also says that if we do self + other it will be the same .

Please elaborate how it will be same?

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 67,989 Points

Great question! Let's step through what is happening.

When a Die is added to another Die, the following happens

In the r_add function we do int(self) + other , which I understand will add self.value and other

BUT ! Kenneth also says that if we do self + other it will be the same .

The method __radd__, a reflected add, is called when D6 is on the right side of the addition operator.

The code below (with return int(self) + other) produces the following results. Comments added after output.

$ python
Python 3.6.3 (default, Oct  3 2017, 21:45:48) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dice import *
>>> d1 = D6(value= 2)
running D6.__init__  # called when instantiating D6 
running Die.__init__ # called by super from D6
>>> d2 = D6(value= 6)
running D6.__init__  # called when instantiating D6
running Die.__init__  # called by super from D6
>>> d1 + d2
running D6.__add__  # d1 on left side of operator call, so call d1.__add__ with other = d2
running Die.__int__  # d1.__add__ calls int(self) which calls the parent's __int__ => gets int value 2
# now has 2 + d2
running D6.__radd__ with int(self)  # d2 on right side of operator, so call d2.__radd__ with other=2
running Die.__int__  # d2.__radd__ calls parents __int__ on itself => gets integer value 6
# now has 6 + 2
8

If the __radd__ is changed to use self + other then the addition flow changes:

>>> d1 + d2
>>> d1 + d2
running D6.__add__  # d1 on left side of operator call its __add__ with other = d2
running Die.__int__  # d1.__add__ calls int(self) which calls the parent's __int__ => gets int value 2
# now has 2 + d2
# -- same to this point --
running D6.__radd__ with self + other  # d2 on right side of operator, so call d2.__radd__ with other=2
# now has d2 + 2
running D6.__add__  #d2 now on left side of operator, so call d2.__add__ with other=2
running Die.__int__ # d2.__add__ calls int(self) which calls the parent's __int__ => gets int value 6
# now has 6 + 2
8 

So, in the end, using int(self) saves one method call.

Code used in above output:

dice.py
import random


class Die:
    def __init__(self, sides=2, value=0):
        print("running Die.__init__")
        if not sides >= 2:
            raise ValueError("Must have at least 2 sides")
        if not isinstance(sides, int):
            raise ValueError("Sides must be a whole number")
        self.value = value or random.randint(1, sides)

    def __int__(self):
        print("running Die.__int__")
        return self.value


class D6(Die):
    def __init__(self, value=0):
        print("running D6.__init__")
        super().__init__(sides=6, value=value)

    def __eq__(self, other):
        print("running D6.__eq__")
        return int(self) == other

    def __ne__(self, other):
        print("running D6.__ne__")
        return int(self) == other

    def __gt__(self, other):
        print("running D6.__gt__")
        return int(self) > other

    def __lt__(self, other):
        print("running D6.__le__")
        return int(self) < other

    def __ge__(self, other):
        print("running D6.__ge__")
        return int(self) > other or int(self) == other

    def __le__(self, other):
        print("running D6.__le__")
        return int(self) < other or int(self) == other

    def __add__(self, other):
        print("running D6.__add__")
        return int(self) + other

    def __radd__(self, other):
        print("running D6.__radd__ with int(self)")
        return int(self) + other