In [41]:
#import the necessary libraries
import abc

Classes and objects¶

In [474]:
class Rectangle: 
    """An abtract representation of a rectangle"""
    
    #Attributes 
    p1 = (0, 0)
    p2 = (1,2)
    
    #Methods 
    def area(self) : 
        return abs(self.p1[0] - self.p2[0])* abs(self.p1[1] - self.p2[1])
    
    #Setters like Java 
    def setP1(self, p1) : 
        self.p1 = p1 
        
    def setP2(self, p2) : 
        self.p2 = p2 
    
In [478]:
Rectangle.area()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[478], line 1
----> 1 Rectangle.area()

TypeError: Rectangle.area() missing 1 required positional argument: 'self'
In [381]:
rectangle = Rectangle()
In [382]:
(rectangle.p1, rectangle.p2 )
Out[382]:
((0, 0), (1, 2))
In [ ]:
 
In [55]:
rectangle.p1 = (2,1)
In [56]:
rectangle.area()
Out[56]:
1
In [58]:
rectangle.setP1((0,0))
rectangle.area()
Out[58]:
2
In [147]:
class Rectangle: 
    """An abtract representation of a rectangle"""
    
    #Constructor
    def __init__(self, p1=(0,0), p2=(1,2)) : 
        
        self.p1 = p1
        self.p2 = p2
    
    #Methods 
    def area(self) : 
        return abs(self.p1[0] - self.p2[0])* abs(self.p1[1] - self.p2[1])
    
    #Setters like Java 
    def setP1(self, p1) : 
        self.p1 = p1 
        
    def setP2(self, p2) : 
        self.p2 = p2 
    
In [148]:
rectangle = Rectangle()
rectangle.area()
Out[148]:
2
In [149]:
rectangle.p2 = (2,2)
In [150]:
rectangle.area()
Out[150]:
4
In [151]:
class RectanglePrivate: 
    """An abtract representation of a rectangle"""
    
    #Constructor
    def __init__(self, p1=(0,0), p2=(1,2)) : 

        self.__p1 = p1 #private
        self.p2 = p2 #public 
    
    #Methods 
    def area(self) : 
        return abs(self.__p1[0] - self.p2[0])* abs(self.__p1[1] - self.p2[1])
    
    #Setters like Java 
    def setP1(self, p1) : 
        self.__p1 = p1 
        
    def setP2(self, p2) : 
        self.p2 = p2 
In [152]:
rectangle = RectanglePrivate()
rectangle.area()
Out[152]:
2
In [153]:
rectangle.p1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[153], line 1
----> 1 rectangle.p1

AttributeError: 'RectanglePrivate' object has no attribute 'p1'
In [154]:
rectangle.__p1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[154], line 1
----> 1 rectangle.__p1

AttributeError: 'RectanglePrivate' object has no attribute '__p1'
In [ ]:
 
In [135]:
dir(rectangle)
Out[135]:
['_RectanglePrivate__p1',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'area',
 'p2',
 'setP1',
 'setP2',
 'tete',
 'toto']
In [240]:
rectangle.__doc__
Out[240]:
'An abtract representation of a rectangle'
In [137]:
rectangle._RectanglePrivate__p1 = (2,2)
In [146]:
rectangle.setP1((0,0))
rectangle.area()
Out[146]:
2

Method chaining¶

In [243]:
class Rectangle:
    """An object representation of a rectangle"""
    # Constructor
    def __init__(self, p1 = (0,0), p2 = (1,1)):
        self.p1 = p1
        self.p2 = p2
    
    # Methods
    def area(self):
        return abs(self.p1[0] - self.p2[0]) * abs(self.p1[1] - self.p2[1])
    
    # Setters
    def setP1(self, p1):
        self.p1 = p1
        return self
    
    def setP2(self, p2):
        self.p2 = p2
        return self
In [244]:
Rectangle().area()
Out[244]:
1
In [246]:
Rectangle().setP1((-1,-1)).area()
Out[246]:
4

Class object string formating¶

In [250]:
def rect_str(self):
    return f"Rectangle[{self.p1}, {self.p2}] => area={self.area()}"
Rectangle.__str__ = rect_str
In [251]:
print(Rectangle())
Rectangle[(0, 0), (1, 1)] => area=1
In [252]:
Rectangle()
Out[252]:
<__main__.Rectangle at 0x1098a1e10>
In [253]:
str(Rectangle())
Out[253]:
'Rectangle[(0, 0), (1, 1)] => area=1'
In [256]:
def rect_repr(self):
    return f"Rectangle({self.p1}, {self.p2})"
Rectangle.__repr__ = rect_repr
In [257]:
repr(Rectangle())
Out[257]:
'Rectangle((0, 0), (1, 1))'
In [259]:
print(Rectangle())
Rectangle[(0, 0), (1, 1)] => area=1
In [260]:
Rectangle()
Out[260]:
Rectangle((0, 0), (1, 1))

Interfaces and abstract classes¶

In [418]:
class IRectangle(abc.ABC) :   

    @abc.abstractmethod
    def area(self): 
        pass 
    
    @abc.abstractmethod
    def perimeter(self): 
        pass 
In [419]:
class Rectangle(IRectangle) :
    pass
In [420]:
rect = Rectangle()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[420], line 1
----> 1 rect = Rectangle()

TypeError: Can't instantiate abstract class Rectangle with abstract methods area, perimeter
In [421]:
IRectangle.__mro__
Out[421]:
(__main__.IRectangle, abc.ABC, object)
In [422]:
class Rectangle(IRectangle) :
    
    #Constructor
    def __init__(self, p1=(0,0), p2=(1,2)) :         
        self.p1 = p1
        self.p2 = p2
        
    def area(self): 
        return abs(self.p1[0] - self.p2[0])* abs(self.p1[1] - self.p2[1]) 
    
    def perimeter(self) : 
        return (abs(self.p1[0] - self.p2[0]) + abs(self.p1[1] - self.p2[1]) )*2 
In [423]:
rect = Rectangle()
In [424]:
rect.area()
Out[424]:
2
In [425]:
def call_area(rect: IRectangle) : 
    return rect.area()
In [426]:
call_area(rect)
Out[426]:
2
In [427]:
rect: IRectangle = Rectangle()
In [428]:
rect.area()
Out[428]:
2
In [429]:
type(rect)
Out[429]:
__main__.Rectangle
In [167]:
class AbstractRectangle(abc.ABC) :   
    
    #Constructor
    def __init__(self, p1=(0,0), p2=(1,2)) :         
        self.p1 = p1
        self.p2 = p2

    @abc.abstractmethod
    def area(self): 
        pass 
    
    def perimeter(self) : 
        return (abs(self.p1[0] - self.p2[0]) + abs(self.p1[1] - self.p2[1]) )*2 
In [179]:
rect = AbstractRectangle()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[179], line 1
----> 1 rect = AbstractRectangle()

TypeError: Can't instantiate abstract class AbstractRectangle with abstract method area
In [180]:
class Rectangle(AbstractRectangle) :
    
    def area(self): 
        return abs(self.p1[0] - self.p2[0])* abs(self.p1[1] - self.p2[1]) 
In [182]:
rect = Rectangle()
rect.area()
rect.perimeter()
Out[182]:
6

Inheratance in Python¶

In [223]:
class Square(Rectangle):
    pass
In [226]:
square = Square()
square.area()
Out[226]:
2

Overriding methods during inheritance¶

In [262]:
class Square(Rectangle):
    def __init__(self, p1=(0,0), l=1):
        assert isinstance(l, (float, int)), "l must be a number"
        p2 = (p1[0]+l, p1[1]+l)
        self.l  = l
        super().__init__(p1, p2)
    
    def setP1(self, p1):
        self.p1 = p1
        self.p2 = (self.p1[0]+self.l, self.p1[1]+self.l)
        return self
    
    def setP2(self, p2):
        raise RuntimeError("Squares take l not p2")
    
    def setL(self, l):
        assert isinstance(l, (float, int)), "l must be a number"
        self.l  = l
        self.p2 = (self.p1[0]+l, self.p1[1]+l)
        return self
    
    def __repr__(self):
        return f"square({self.p1}, {self.l})"
In [263]:
square = Square()
square
Out[263]:
square((0, 0), 1)
In [264]:
Square().area()
Out[264]:
1
In [265]:
Square().setP1((-1,-1)).area()
Out[265]:
1
In [266]:
Square().setL(2).area()
Out[266]:
4
In [267]:
Square((0,0), (1,1))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[267], line 1
----> 1 Square((0,0), (1,1))

Cell In[262], line 3, in Square.__init__(self, p1, l)
      2 def __init__(self, p1=(0,0), l=1):
----> 3     assert isinstance(l, (float, int)), "l must be a number"
      4     p2 = (p1[0]+l, p1[1]+l)
      5     self.l  = l

AssertionError: l must be a number
In [268]:
Square().setL((0,0))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[268], line 1
----> 1 Square().setL((0,0))

Cell In[262], line 17, in Square.setL(self, l)
     16 def setL(self, l):
---> 17     assert isinstance(l, (float, int)), "l must be a number"
     18     self.l  = l
     19     self.p2 = (self.p1[0]+l, self.p1[1]+l)

AssertionError: l must be a number
In [269]:
Square().setP2((0,0))
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[269], line 1
----> 1 Square().setP2((0,0))

Cell In[262], line 14, in Square.setP2(self, p2)
     13 def setP2(self, p2):
---> 14     raise RuntimeError("Squares take l not p2")

RuntimeError: Squares take l not p2

Making an object iterable¶

In [270]:
class Rectangle:
    """An object representation of a rectangle"""
    # Constructor
    def __init__(self, p1 = (0,0), p2 = (1,1)):
        self.p1 = p1
        self.p2 = p2
    
    # Methods
    def area(self):
        return abs(self.p1[0] - self.p2[0]) * abs(self.p1[1] - self.p2[1])
    
    # implemented methods
    def __iter__(self):
        return iter([self.p1, (self.p1[0], self.p2[1]), self.p2, (self.p2[0], self.p1[1])])

    # Setters
    def setP1(self, p1):
        self.p1 = p1
        return self
    
    def setP2(self, p2):
        self.p2 = p2
        return self
In [271]:
for pt in Rectangle():
    print(pt)
(0, 0)
(0, 1)
(1, 1)
(1, 0)
In [280]:
class Rectangle:
    def __init__(self, p1 = (0,0), p2 = (1,1)):
        self.p1 = p1
        self.p2 = p2
        self.vertices = [self.p1, (self.p1[0], self.p2[1]),
                         self.p2, (self.p2[0], self.p1[1]) ]
        self.index = 0
  
    # Methods
    def area(self):
        return abs(self.p1[0] - self.p2[0]) * abs(self.p1[1] - self.p2[1])
    
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == len(self.vertices):
            self.index = 0
            raise StopIteration
        v = self.vertices[self.index]
        self.index += 1
        return v
    
    def __repr__(self):
        return f"Rectangle({self.p1}, {self.p2})"
    
    def __str__(self) : 
        return f"Rectangle[{self.p1}, {self.p2}] => area={self.area()}"
    # Setters
    def setP1(self, p1):
        self.p1 = p1
        return self
    
    def setP2(self, p2):
        self.p2 = p2
        return self
    
In [281]:
r = Rectangle()
for pt in r:
    print(pt)
(0, 0)
(0, 1)
(1, 1)
(1, 0)
In [282]:
r.area()
Out[282]:
1
In [283]:
r
Out[283]:
Rectangle((0, 0), (1, 1))
In [284]:
dir(r)
Out[284]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__next__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'area',
 'index',
 'p1',
 'p2',
 'setP1',
 'setP2',
 'vertices']
In [ ]:
 
In [ ]:
 

Some problems with multi-heritage¶

In [437]:
class Worm:
    def __init__ (self, name) : 
        self.name = name
        
    def eat(self): 
        print(self.name +" swallows")
        
class Fly:
    
    def __init__ (self, name) : 
        self.name = name
        
    def move(self): 
        print("I'm flying")
    
    def eat(self): 
        print(self.name +" is nibbling...")
In [438]:
class ButterFly(Worm, Fly): 
    def __init__(self, name):
        self.name = name
In [439]:
butterfly = ButterFly("BoBo")
In [440]:
butterfly.eat()
BoBo swallows
In [441]:
class ButterFly(Fly, Worm): 
    def __init__(self, name):
        self.name = name
        
butterfly = ButterFly("BoBo")
butterfly.eat()
BoBo is nibbling...

what is going on here?¶

Method Resolution Order

The method resolution order (or MRO) tells Python how to search for inherited methods. This comes in handy when you’re using super() because the MRO tells you exactly where Python will look for a method you’re calling with super() and in what order.

Every class has an __mro__ attribute that allows us to inspect the order, so let’s do that:

In [304]:
dir(FlyingReptile)
Out[304]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'eat',
 'eat_bird',
 'fly']
In [350]:
FlyingReptile.__mro__
Out[350]:
(__main__.FlyingReptile, __main__.Reptile, __main__.Bird, object)
In [306]:
Rectangle.__mro__
Out[306]:
(__main__.Rectangle, object)
In [ ]:
 

how to avoid that?¶

In [451]:
class ButterFly(Worm, Fly): 
    def __init__(self, name):
        self.name = name
    def eat_likefly(self): 
        return Fly.eat(self) ## or super(Worm, self).eat() in some cases
In [452]:
butterfly = ButterFly("Butterfly")
In [453]:
butterfly.eat_likefly()
Butterfly is nibbling...
In [448]:
fly = Fly("Fly")
fly.eat()
Fly is nibbling...
In [449]:
FlyingReptile.__mro__
Out[449]:
(__main__.FlyingReptile, __main__.Reptile, __main__.Bird, object)
In [450]:
butterfly.eat()
Butterfly swallows
In [456]:
class AbstractWorm(abc.ABC):
    def __init__ (self, name) : 
        self.name = name
    
    @abc.abstractmethod
    def eat(self, food): 
        pass
        
class AbstractFly(abc.ABC):
    
    def __init__ (self, name) : 
        self.name = name
    
    @abc.abstractmethod
    def fly(self): 
        pass 
    
    @abc.abstractmethod
    def eat(self): 
        pass
In [457]:
class AbstractButterFly(AbstractFly, AbstractWorm): 
    pass 

butterfly = AbstractButterFly("BoBo")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[457], line 4
      1 class AbstractButterFly(AbstractFly, AbstractWorm): 
      2     pass 
----> 4 butterfly = AbstractButterFly("BoBo")

TypeError: Can't instantiate abstract class AbstractButterFly with abstract methods eat, fly
In [459]:
class AbstractButterFly(AbstractFly, AbstractWorm): 
    def eat(self) : 
        pass 
    
    def fly(self): 
        pass 

butterfly = AbstractButterFly("BoBo")
butterfly.eat()
In [460]:
class AbstractButterFly(AbstractFly, AbstractWorm): 
    def eat(self) : 
        print(self.name +" is nibbling")
    def fly(self): 
        print(self.name + "is flying... goodbye.." )
In [462]:
butterfly = AbstractButterFly("BoBo")
In [463]:
butterfly.eat()
BoBo is nibbling
In [469]:
class AbstractWorm(abc.ABC):
    def __init__ (self, name) : 
        self.name = name
    
    @abc.abstractmethod
    def eat(self): 
        pass
        
class AbstractFly(abc.ABC):
    
    def __init__ (self, name) : 
        self.name = name
    
    @abc.abstractmethod
    def fly(self): 
        pass 
    
    def eat(self): 
        print("Bird "+self.name + " is nibbling")
In [470]:
AbstractWorm()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[470], line 1
----> 1 AbstractWorm()

TypeError: Can't instantiate abstract class AbstractWorm with abstract method eat
In [471]:
class AbstractButterFly(AbstractFly, AbstractWorm): 
    def fly(self): 
        print(self.name + "is flying... goodbye.." )
butterfly = AbstractButterFly("BoBo")
In [472]:
butterfly.eat()
Bird BoBo is nibbling
In [473]:
class AbstractButterFly(AbstractWorm, AbstractFly): 
    def fly(self): 
        print(self.name + "is flying... goodbye.." )
butterfly = AbstractButterFly("BoBo")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[473], line 4
      2     def fly(self): 
      3         print(self.name + "is flying... goodbye.." )
----> 4 butterfly = AbstractButterFly("BoBo")

TypeError: Can't instantiate abstract class AbstractButterFly with abstract method eat
In [482]:
class Animal: 
    
    def display(self) : 
        print("I'm a cute animal")
        
class Dog(Animal) : 
    
    def display(self) : 
        print("I'm a cute dog animal")
In [483]:
dog = Dog() 
dog.display()
I'm a cute dog animal
In [484]:
Dog.__mro__
Out[484]:
(__main__.Dog, __main__.Animal, object)
In [485]:
dir(Dog)
Out[485]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'display']
In [ ]: