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
Anthony Grodowski
4,902 Pointssuper().__init__()
Why are we putting super().__init__() in the Hand class? I know that it creates an empty list so we can add stuff to that, but isn't the Hand list enough? I tried to run the program without this line and it still works... Just the stuff was appended to Hand itself. Also what confuses me is self after super().__init__(). To which instance(am I calling it right?) this self refers to? The new list created in super().__init__() or the Hand instance?
from dice import D6
class Hand(list):
def __init__(self, size=0, die_class=None, *args, **kwargs): #size - the number of dice to roll
if not die_class:
raise ValueError("You must provide a die_class")
super().__init__()
for _ in range(size):
self.append(die_class())
self.sort()
def _by_value(self, value):
dice = []
for die in self:
if die == value:
dice.append(die)
return dice
class YatzyHand(Hand):
def __init__(self, *args, **kwargs):
super().__init__(size=5, die_class=D6, *args, **kwargs)
@property
def ones(self):
return self._by_value(1)
@property
def twos(self):
return self._by_value(2)
@property
def threes(self):
return self._by_value(3)
@property
def fours(self):
return self._by_value(4)
@property
def fives(self):
return self._by_value(5)
@property
def sixes(self):
return self._by_value(6)
@property
def total_dict(self):
return {
1: len(self.ones),
2: len(self.twos),
3: len(self.threes),
4: len(self.fours),
5: len(self.fives),
6: len(self.sixes)
}
Another thing that confuses me is how does python know that we want the dices to be sorted by the value even tho we didn't provide that information? I mean in
for _ in range(size):
self.append(die_class())
self.sort()
we're sorting an instance which is a list consisting non-numeric objects but with value attributes. How python knows that we want these objects to be sorted by value attribute?
1 Answer
Chris Freeman
Treehouse Moderator 68,468 PointsGood questions!
Why are we putting super().__init__() in the Hand class? I know that it creates an empty list so we can add stuff to that, but isn't the Hand list enough?
To clarify, neither __init__, or the super().__init__ creates the instance. The instance is created by the __new__() method then Hand.__init__() is called and then, if present, super().__init__ is called. The list.__init__() method does not appear to add anything to the instance. All of the list methods are added to the Hand instance in the __new__() method.
All of the self references within the Hand.__init__() method refer to the self parameter of the Hand.__init__() method. This is assigned a value when list.__new__() creates the instance then calls self.__init__(), which is the has been overridden to be Hand.__init__().
Also what confuses me is self after super().__init__(). To which instance (am I calling it right? [yes!] ) this self refers to? The new list created in super().__init__() or the Hand instance?
No instance is created in super().__init__(). All self references are to the Hand instance.
Another thing that confuses me is how does python know that we want the dices to be sorted by the value even tho we didn't provide that information?
The list sort method:
This method sorts the list in place, using only
<comparisons between items
This means it calls the __lt__() method on each item in list. So for self.sort() to work, Die.__lt__() must be defined.
we're sorting an instance which is a list consisting non-numeric objects but with value attributes. How python knows that we want these objects to be sorted by value attribute?
For each pairwise comparison in the list of, say, item a and item b, Python blindly calls the a.__lt__(b) method. If it doesn't exist, or it can't properly compare a and b, then a TypeError: '<' not supported between instances.... is raised.
To see this in action, look at the modified code and its output:
class Die:
def __init__(self, sides=2, value=None):
if sides < 2:
raise ValueError("Can't have fewer than two sides")
self.sides = sides
if not value:
self.value = random.randint(1, sides)
else:
self.value = value
print("in Die.__init__")
def __int__(self):
print("in Die.__int__")
return self.value
def __lt__(self, other):
print("in Die.__lt__")
return int(self.value) < int(other.value)
def __add__(self, other):
print("in Die.__add__")
return int(self) + other
def __radd__(self, other):
print("in Die.__radd__")
return self + other
class D20(Die):
def __init__(self):
print("in D20.__init__")
super().__init__(sides=20)
class Hand(list):
def __init__(self, size=0, die_class=None, *args, **kwargs):
print("in Hand.__init__")
#size - the number of dice to roll
if not die_class:
raise ValueError("You must provide a die_class")
super().__init__()
for _ in range(size):
self.append(die_class())
self.sort()
def _by_value(self, value):
print("in Hand.__by_value__")
dice = []
for die in self:
if die == value:
dice.append(die)
return dice
def sort(self, *args, **kwargs):
print(f"enter Hand.__sort__: {[int(x) for x in self]}")
super().sort(*args, **kwargs)
print(f"exit Hand.__sort__: {[int(x) for x in self]}")
>>> h5 = Hand(5, D20)
>>> h5 = Hand(5, D20)
in Hand.__init__
in D20.__init__
in Die.__init__
in D20.__init__
in Die.__init__
in D20.__init__
in Die.__init__
in D20.__init__
in Die.__init__
in D20.__init__
in Die.__init__
in Die.__int__
in Die.__int__
in Die.__int__
in Die.__int__
in Die.__int__
enter Hand.__sort__: [9, 2, 1, 15, 2]
in Die.__lt__
in Die.__lt__
in Die.__lt__
in Die.__lt__
in Die.__lt__
in Die.__lt__
in Die.__lt__
in Die.__int__
in Die.__int__
in Die.__int__
in Die.__int__
in Die.__int__
exit Hand.__sort__: [1, 2, 2, 9, 15]
Post back if you have more questions. Good luck!!
Anthony Grodowski
4,902 PointsAnthony Grodowski
4,902 PointsThank you so much for as always very profesional answer! So if "The
list.__init__() methoddoes not appear to add anything to the instance. All of the list methods are added to theHandinstance in the__new__()method.", why is it there?Another thing I think I don't have a clear view on is the flow while creating a
Handinstance. As far as I'm concerned it goes like this:Hand.__new__()and that's when the instance inherits all of the list's methods and attributes (or maybe it's in thelist.__new__()method??).Hand.__init__()super().__init__(), which could also be considered aslist.__init__(), but I don't know for what reason.(Maybe my incomprehension is caused by your few mistypes in "This is assigned a value when
list.__new__()creates the instance then callsself.__init__(), which is the has been overridden to beHand.__init__()")Also one thing popped into my mind: in
it's appending our instance with
die_class(), for example withD20()and if i try to print this instance I get back for example[1, 1, 2, 4, 5], so it's a list withD20().value. How did it happen if we didn't explicitly called that we want this list to be filled with.valueattributes instead ofD20()instances?Chris Freeman
Treehouse Moderator 68,468 PointsChris Freeman
Treehouse Moderator 68,468 PointsWhy is
super().__init__()there? As a programmer, you don't always know ahead of time if the parent's__init__()method does anything useful. Also, it's possible that the parent's__init__()may change in the future, so by adding thesuper(), the current file won't have to be changed if the parent changes.There is another quirk: where are
Hand.__init__*argsand**kwargsbeing used? Ideally, they would be included in thesuper().__init__(*args, **kwargs)call to pass any extra arguments to the parent classes' initialization methods.I don't have a clear view on is the flow while creating a
Handinstance. It goes like this:Hand(5, D20)Hand.__new__(5, D20)is called. Since it was not defined, the inherited version inlist.__new__(5, D20)is runlist.__new__creates a new instance.selfnow points to this instanceself.__init__(5, D20)is called. Since it exists inHand,Hand.__init__(self, 5, D20)is runHand.__init__callssuper().__init__(), this is the parent'slistversionlist.__init__list.__init__doesn't do anything significantlist.__init__completes, flow returns to thesuper()call inHand.__init__()Hand.__init__completes, it returns None by default!. Flow returns tolist.__new__list.__new__that returns the instance to theHand(5, 20)callRemember, just because a method comes from a parent class, it is executed as if it is in the current class. In other words, it's as if
list.__new__had been copied intoHandasHand.__new__I corrected the paramgraph below in my answer above:
When appending our
Handinstance withdie_class(), for example withD20(). How is theD20value output when runningprint(hand_instance)? The value printed depends on whether there is a__str__method present. If no__str__method present, then__repr__is run. If neither, than the location of the object in memory is printed. If classDieis updated to include the__repr__method below, then instead of printing the memory location of theD20object, it's value is printed.