I've been struggling to understand the details of this code for a couple of days now:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
super().__init__(**kwargs)
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
# Square inherits from Rectangle:
class Square(Rectangle):
def __init__(self, length, **kwargs):
super().__init__(length=length, width=length, **kwargs)
# Triangle doesn't inherit from any class:
class Triangle:
def __init__(self, base, height, **kwargs):
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
# Pyramid inherits from square and triangle:
class Pyramid(Square, Triangle):
def __init__(self, base, slant_height, **kwargs):
self.base = base
self.slant_height = slant_height
# Adding attributes to kwargs dictionary
kwargs["height"] = slant_height
kwargs["length"] = base
super().__init__( base=base, **kwargs)
def area(self):
base_area = super().area()
perimeter = super().perimeter()
return 0.5 * perimeter * self.slant_height + base_area
def area_2(self):
base_area = super().area()
triangle_area = super().tri_area()
return triangle_area * 4 + base_area
The inheritance tree is as such:
Rectangle
\
Square Triangle
\ /
Pyramid
The MRO for the Pyramid
class:
(__main__.Pyramid,
__main__.Square,
__main__.Rectangle,
__main__.Triangle,
object)
I understand the general idea behind the code, and how super().__init__
is used to pass a dictionary of variable keyword arguments to the superclass using **kwargs
. The details I'm having issues with are:
Pyramid
class, use base=base
when the superclass (Square
) takes a length
argument?super().__init__(base, **kwargs)
instead of base=base
, why does it give an error saying that init() got multiple values for argument 'length', is it because of the kwargs["length"]
specified above it? If so, how does base=base solve that?super().__init__(**kwargs)
in Rectangle when it doesn't have a superclass? My guess is maybe to pass the kwargs to the built-in object
base class so Triangle
can inherit from it? But I'm not sure.Triangle
also have a call to the base class too? It seems redundant (even removing super().__init__(**kwargs)
from Triangle doesn't change anything).Good questions!
Because Square.__init__
accepts **kwargs
we can actually pass whatever we want to it without getting an unexpected keyword argument
error1. This will become handy a bit later. Any keyword argument that it (and/or its superclasses' __init__
methods) does not consume will propagate up in the MRO until we get to object
.
super().__init__(**kwargs)
in Triangle.__init__
breaks this behavior.
Due to the expansion of kwargs
, when calling super().__init__(base, **kwargs)
in Pyramid.__init__
, Square.__init__
is actually called with (2, height=3, length=2)
. Since the argument of Square.__init__
is called length
you recieve the error about multiple values for length
.
That's correct. Furthermore, because nothing "consumed" the base
keyword argument yet, Triangle.__init__
will receive it.
That's also correct. There is absolutely no need to call super().__init__(**kwargs)
in Triangle.__init__
, especially that by that point kwargs
is an empty dict (all the keyword arguments it contained were consumed already).