Just trying to create a simple toy example to calculate the surface area of a pyramid:
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 perim(self):
return 2 * (self.length + self.width)
class Square(Rectangle):
def __init__(self, length, **kwargs):
super().__init__(length = length, width = length, **kwargs)
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
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
return super().area() + 4 * super().tri_area()
p = Pyramid(2,4)
p.surf_area()
But this gives me an error of
AttributeError Traceback (most recent call last)
<ipython-input-154-d97bd6ab2312> in <module>
1 p = Pyramid(2,4)
----> 2 p.surf_area()
<ipython-input-153-457095747484> in surf_area(self)
6
7 def surf_area(self):
----> 8 return super().area() + 4 * super().tri_area()
<ipython-input-151-8b1d4ef9dca9> in tri_area(self)
5 super().__init__(**kwargs)
6 def tri_area(self):
----> 7 return 0.5 * self.base * self.height
AttributeError: 'Pyramid' object has no attribute 'base'
The online resources don't seem to give much of a conceptual understanding of **kwargs too well (or they're written in too much a labrintyine manner for a beginner). Does this somehow have to do with the fact that **kwargs as an iterable need to be exhausted completely before going to the next call?
I can sort of understand why you'd be confused. The main trick to realise is this: absolute bottom line, kwargs
is not something magical. It is just a dictionary holding key value pairs. If a call requires more positional arguments than provided, it can look into keyword arguments and accept some values. However, kwargs does not invoke some magic that associates all names provided as a self.<some_name_here>
.
So, first to just get a visual understanding of what's going on, what you should do when you don't understand a piece of code is making sure it runs how you think it does. Let's add a couple print statements and see what's happening.
Version 1:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
print(f"in rectangle. length = {self.length}, width = {self.width}")
def area(self):
return self.length * self.width
class Square(Rectangle):
def __init__(self, length, **kwargs):
print("in square")
super().__init__(length = length, width = length, **kwargs)
class Triangle:
def __init__(self, base, height, **kwargs):
print("in triangle")
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
print("in pyramid")
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
print(f"area : {super().area()}")
print(f"tri_area : {super().tri_area()}")
return super().area() + 4 * super().tri_area()
p = Pyramid(2,4)
p.surf_area()
Output:
in pyramid
in square
in rectangle. length = 2, width = 2
area : 4
#and then an error, note that it occurs when calling super().tri_area()
#traceback removed for brevity.
AttributeError: 'Pyramid' object has no attribute 'base'
I suspect this already breaks some assumptions you had about how the code runs. Notice that the triangle's init was never called. But let's get rid of the parts that work fine, and add an additional print statement. I will also take the liberty of calling it with a different value for first argument, something that pops out easier.
Version 2:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
print(f"in rectangle. length = {self.length}, width = {self.width}")
print(f"kwargs are: {kwargs}")
class Square(Rectangle):
def __init__(self, length, **kwargs):
print("in square")
super().__init__(length = length, width = length, **kwargs)
class Triangle:
def __init__(self, base, height, **kwargs):
print("in triangle")
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
print("in pyramid")
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
print(f"tri_area : {super().tri_area()}")
return super().tri_area()
p = Pyramid(10000,4)
p.surf_area()
Output:
in pyramid
in square
in rectangle. length = 10000, width = 10000
kwargs are: {'base': 10000, 'height': 4}
#error with traceback
AttributeError: 'Pyramid' object has no attribute 'base'
Bottom line: the kwargs holds a key with the name base, but this has no relation to self.base
. However, my recommendation is to get rid of the whole class structure, and spend some time playing around with any basic function, get rid of the extra stuff.
Say, a demonstration:
def some_func(a, b, **kwargs):
print(a, b)
print(kwargs)
some_func(1, 2)
some_func(1, 2, c=42)
some_func(a=1, c=42, b=2)
def other_func(a, b, **look_at_me):
print(a, b)
print(look_at_me)
other_func(1, 2)
other_func(1, 2, c=42)
other_func(a=1, c=42, b=2)
These two chunks produce the same outputs. No magic here. Output:
1 2
{}
1 2
{'c': 42}
1 2
{'c': 42}
When you added the classes into the mix, and inheritance, there's too many things happening at once. It is easier to miss what happens, so it's a good idea to use smaller code samples.