Python OOP Concepts

2 minute read

OOP Concepts in PyThon

Introduction

This series of tutorials is to help anyone who might need to review some OOP concepts in Python. I will try to add diagrams and code to explain more intuitively.

ClassMethods

class Database:
    content = {'users': []}
    
    @classmethod
    def insert(cls, data):
        cls.content['users'].append(data)
        
    @classmethod
    def remove(cls, finder):
        cls.content['users'] = [user for user in cls.content['users'] if not finder(user)]
        
    @classmethod
    def find(cls, finder):
        return [user for user in cls.content['users'] if finder(user)]
    
    @classmethod
    def print_content(cls):
        return cls.content

Since content is a class variable, not an instance variable; hence, it will not change per object. If we create multiple objects of type database, they will all have the same content variable.

d1 = Database()
d2 = Database()
print(d1.print_content())
print(d2.print_content())

OUTPUT:
> {'users': []}
> {'users': []}

d1.insert('example1')
print(d1.print_content())
print(d2.print_content())

OUTPUT:
> {'users': ['example1']}
> {'users': ['example1']}

d2.insert('example2')
print(d1.print_content())
print(d2.print_content())

OUTPUT:
> {'users': ['example1', 'example2']}
> {'users': ['example1', 'example2']}

Inheritance

#user.py
class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password
        
    def login(self):
        return 'Logged in!'
    
    def __repr__(self):
        return f'<User {self.username}>'
    

#saveable.py
class Saveable:
    def save(self):
        Database.insert(self.to_dict())


# databse.py
class Database:
    content = {'users': []}
    
    @classmethod
    def insert(cls, data):
        Database.content['users'].append(data)
        # or cls.content['users'].append(data)

    @classmethod
    def remove(cls, finder):
        cls.content['users'] = [user for user in cls.content['users'] if not finder(user)]
        
    @classmethod
    def find(cls, finder):
        return [user for user in cls.content['users'] if finder(user)]
    
    
#admin.py
class Admin(User, Saveable):
    def __init__(self, username, password, access):
        super(Admin, self).__init__(username, password)
        self.access = access
        
    def __repr__(self):
        return f'<Admin {self.username}, access {self.access}>'
    
    def to_dict(self):
        return {
            'username': self.username,
            'password': self.password, 
            'access': self.access
        }
    

So in the above example, we have Admin class inheriting from both User and Saveable class. From app.py, we will create an object of Admin class.

#app.py

a = Admin('rolf', '1234', 3)
a.save()

print(Database.content)
print(Database.find(lambda x:x['username'] == 'rolf'))

OUTPUT:
<Admin rolf, access 3>
{'users': [{'username': 'rolf', 'password': '1234', 'access': 3}]}
[{'username': 'rolf', 'password': '1234', 'access': 3}]

Abstract Base Class

from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    def walk(self):
        print('walking..')
        
    @abstractmethod
    def num_legs(self):
        pass

Since num_legs in Animal is an abstract class, all child classes that inherit Animal needs to implement the method num_legs.

class Dog(Animal):
    def __init__(self, name):
        self.name = name
        
    def num_legs(self):
        return 4
    
class Monkey(Animal):
    def __init__(self, name):
        self.name = name
        
    def num_legs(self):
        return 2

dog = Dog('Grover')
print(dog.num_legs())

OUTPUT:
> 4

ABCs and Interfaces

from abc import ABCMeta, abstractmethod
class Saveable(metaclass=ABCMeta):
    def save(self):
        Database.insert(self.to_dict())
        
    @abstractmethod
    def to_dict(self):
        pass
    
# databse.py
class Database:
    content = {'users': []}
    
    @classmethod
    def insert(cls, data):
        Database.content['users'].append(data)
        # or  cls.content['users'].append(data)

    @classmethod
    def remove(cls, finder):
        cls.content['users'] = [user for user in cls.content['users'] if not finder(user)]
        
    @classmethod
    def find(cls, finder):
        return [user for user in cls.content['users'] if finder(user)]

Since to_dict is an abstract method, any class that will inherit from Saveable will have to implement the to_dict method. In our case, both User and Admin inherits Saveable, so they both have to implement the to_dict method.

#user.py
class User(Saveable):
    def __init__(self, username, password):
        self.username = username
        self.password = password
        
    def login(self):
        return 'Logged in!'
    
    def __repr__(self):
        return f'<User {self.username}>'
    
    def to_dict(self):
        return {
            'username': self.username,
            'password': self.password
        }
    
#admin.py    
class Admin(User, Saveable):
    def __init__(self, username, password, access):
        super(Admin, self).__init__(username, password)
        self.access = access
        
    def __repr__(self):
        return f'<Admin {self.username}, access {self.access}>'
    
    def to_dict(self):
        return {
            'username': self.username,
            'password': self.password, 
            'access': self.access
        }

#app.py

a = Admin('rolf', '1234', 3)
a.save()

print(Database.content)
print(Database.find(lambda x:x['username'] == 'rolf'))

OUTPUT:

{'users': [{'username': 'rolf', 'password': '1234', 'access': 3}]}
[{'username': 'rolf', 'password': '1234', 'access': 3}]

Tags:

Updated: