Python OOP

Python OOP

Introduction to Object Oriented Programming in Python.

Here is an explanation of OOP, Python's multi-paradigm nature and OOP in Python:

OOP stands for Object Oriented Programming. It is a programming paradigm that organizes software design around data (objects) rather than functions and logic. Some key concepts of OOP are:

Python is a multi-paradigm language, meaning it supports OOP as well as procedural and functional programming. Some features that make Python object-oriented are:

  • Classes - Python has full support for OOP with classes. A class defines the properties and behaviors of an object.

  • Inheritance - A class can inherit from another class to acquire its properties and behaviors.

  • Polymorphism - Python supports polymorphism through operator overloading and method overloading.

  • Encapsulation - Python supports encapsulation through its use of public, private and protected attributes.


Classes

In Python, classes are defined using the class keyword. For example:

class Dog:
    species = "Canis familiaris"

    def __init__(self, name, age):
        self.name = name
        self.age = age

Here we have defined a Dog class with attributes species, name and age. We use the init() method to initialize objects created from this class. Objects are instantiated using the class name:

fido = Dog("Fido", 3)

Constructor:

The init() method is called automatically whenever we create a new instance of the class using the class name. For example:

class Dog:
    def __init__(self, breed):
        self.breed = breed

fido = Dog("Labrador")

Here, when we create the Dog instance fido, the init() method is called automatically and passes the argument "Labrador" to the method.

The init() method is used to:

  • Initialize or assign values to object attributes. The attributes can be initialized by referencing the arguments passed to the init() method.

  • Perform any other initialization tasks required when the object is created.

The init() method is called before any other method when an object is created.

So in summary, the init() method is used as a constructor to initialize the attributes of an object when it is created.


Self Parameter

The self parameter in Python serves a similar purpose as the this keyword in Java, but with some differences:

In Java:

  • this refers to the current instance of the class.

  • It is used to refer to the current object's members and methods.

In Python:

  • self refers to the current instance of the class.

  • It is used to refer to the current object's members and methods.

  • But self is just a convention, not a keyword like this in Java.

  • self is passed as the first argument to method definitions.

For example:

Java:

Java:public class Dog {
    String name;

    void bark() {
        System.out.println(this.name + " barks!");    
    }
}

Python:

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(self.name + " barks!")

So in both cases:

  • self/this refers to the current instance

  • It is used to access instance attributes and methods

The differences are:

  • self is a parameter in Python method definitions, while this is a keyword in Java

  • self is a convention in Python, while this is part of the Java language

In summary, while self and this serve a similar purpose, there are some syntactic differences:

  • "self" is passed as an explicit parameter in Python

  • "this" is a keyword in Java

But conceptually, they both allow you to refer to the current instance's attributes and methods from within its own methods.


Hierarchy

Python has a simple but flexible class hierarchy:

At the top of the hierarchy is the object class:

  • All classes in Python inherit (either directly or indirectly) from the object class.

  • The object class defines some basic methods like init(), str(), etc.

  • When a class does not inherit from any other class, it implicitly inherits from object.

For example:

class Dog:
    pass

# This is equivalent to:
class Dog(object): 
    pass

So the class hierarchy for Dog would be:

object > Dog

We can define our own base classes that act as superclasses for other classes:

class Animal:
    pass

class Dog(Animal):
    pass

class Cat(Animal): 
    pass

Here Animal is a base class that Dog and Cat inherit from. The class hierarchy would be:

object > Animal > Dog
object > Animal > Cat

The benefits of a class hierarchy are:

  • Code reuse: Base classes define attributes and methods that child classes can inherit.

  • Polymorphism: Child classes can override methods from base classes.

  • Encapsulation: Internal implementation is hidden in base classes.

Some other points:

  • Multiple inheritance is supported in Python, so a class can inherit from multiple base classes.

  • Python uses MRO (Method Resolution Order) to resolve methods when a class inherits from multiple base classes.

  • Abstract base classes (from the abc module) can be used to define interfaces that child classes must implement.

In summary, the object class acts as the root of Python's simple class hierarchy. We can define our own base classes to achieve code reuse, encapsulation and polymorphism.


Encapsulation

Encapsulation in Python is achieved by using attributes with two leading underscores (__) and accessing them using getter and setter methods instead of directly accessing the attributes. This allows us to control how the attributes are accessed and modified.

For example:

class Dog:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, name):
       self.__name = name

Here we have a name attribute with two leading underscores. This makes it a "private" attribute - it cannot be accessed directly outside the class.

We then define getter and setter methods to access and modify the attribute:

fido = Dog("Fido")
print(fido.get_name()) # Prints Fido
fido.set_name("Spot")
print(fido.get_name()) # Prints Spot

The benefits of encapsulation are:

  • Hiding internal implementation details - only relevant methods and attributes are exposed.

  • Controlling access to attributes - by using getter and setter methods we can add validation logic.

  • Flexibility to change implementation without affecting client code - since clients use getter/setter methods.

So in summary, encapsulation in Python is achieved through name mangling (using double underscores) and getter/setter methods to control attribute access. This provides data hiding and flexibility in our class design.


Inheritance

Inheritance allows us to define a class that inherits the properties and behaviors of another class.

For example:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

We can define a Dog class that inherits from the Animal class:

class Dog(Animal):
    def speak(self):
        print("Woof! My name is", self.name)

The Dog class inherits the init() method and name attribute from the Animal class.
It overrides the speak() method to define specific behavior for dogs.

We can instantiate the Dog class:

fido = Dog("Fido")
fido.speak()
# Output: 
# Woof! My name is Fido

Notes:

  • The subclass inherits all the public methods of its parent class.

  • The subclass can define new methods and attributes in addition to the ones it inherits.

  • The subclass can override or modify the existing methods of its parent class.

  • The inheritance relationship is established by listing the parent class in the class definition.

  • We use the super() function in the child's init() method to call the parent's init() method.

In summary, Inheritance allows us to reuse code from an existing class and extend that class to create new classes with different behavior through method overriding and adding new methods and attributes.


Single Inheritance in Python

In single inheritance, a child class inherits from one parent class. For example:

class Parent:
    pass

class Child(Parent): 
    pass

Here Child inherits from Parent, so it is a single inheritance relationship.

Compared to Java:

  • Python uses the class name in parentheses to inherit, while Java uses the extends keyword.

  • Python allows multiple inheritance, while Java only allows single inheritance.

Multiple Inheritance in Python

In multiple inheritance, a child class inherits from more than one parent class. For example:

class Parent1:
    pass

class Parent2:
    pass

class Child(Parent1, Parent2):
    pass

Here Child inherits from both Parent1 and Parent2, so it is a multiple inheritance relationship.

Compared to Java:

  • As mentioned, Java only allows single inheritance, not multiple inheritance.

  • Python does not have interfaces like Java. It uses duck typing instead - if an object walks like a duck and quacks like a duck, then it is a duck.

The differences between single and multiple inheritance in Python are:

  • Single inheritance is simpler and easier to understand.

  • Multiple inheritance can lead to diamond problems and method resolution issues.

  • Multiple inheritance allows a class to inherit behaviors from multiple parents.

In summary, Python supports both single and multiple inheritance. Compared to Java, Python has a more flexible inheritance model without interfaces. Inheritance in Python is based on duck typing instead.


Polymorphism

Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance.

Polymorphism allows us to perform the same operation in different ways.

For example:

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("Woof!")

class Cat(Animal):        
    def speak(self):
        print("Meow!")

Here we have two subclasses (Dog and Cat) that inherit from the Animal class. Both subclasses override the speak() method defined in the parent class.

Now we can call the speak() method on instances of the subclasses:

dog = Dog()  
cat = Cat()

dog.speak() # Prints "Woof!"
cat.speak() # Prints "Meow!"

Even though we are calling the same speak() method, the output is different based on the subclass instance. This is an example of polymorphism.

Notes:

  • Polymorphism allows us to perform the same operation in different ways depending on the object we are using.

  • Polymorphism is achieved through inheritance. The child classes inherit the interface from the parent class and override methods to define class-specific behavior.

  • Polymorphism simplifies the code and makes it more flexible and extensible.

In summary, polymorphism refers to the ability to define methods in the parent class and allow child classes to override those methods to define class-specific behavior.


Abstract Classes

Python does not have native abstract classes, but we can achieve abstraction using a convention:

  • Define abstract methods by just putting pass in the method body:
from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def make_sound(self):
        pass
  • Classes that inherit from an abstract class must implement all abstract methods, otherwise they are also abstract:
class Dog(Animal):
    def make_sound(self):
        print("Bark!")

class Cat(Animal):
    def make_sound(self):
        print("Meow!")
  • We mark a class as abstract using ABC from the abc module. Any class that inherits from ABC must implement all abstract methods.
from abc import ABC, abstractmethod

class Animal(ABC):    
    @abstractmethod
    def make_sound(self):
        pass

class Dog(Animal):
    def make_sound(self):
        print("Bark!")

# This will raise an exception        
class AbstractCat(Animal):
    pass
  • Abstract classes allow us to define an interface that concrete classes must implement.

So in summary, Python achieves abstraction through:

  1. Defining abstract methods using pass

  2. Marking a class as abstract by inheriting from ABC

  3. Enforcing implementation of abstract methods for non-abstract classes

Python does not have native support for abstract classes, but we can simulate the behavior using the abc module. This allows us to define interfaces that concrete classes must implement.


Class Attributes

Class attributes are attributes that are shared among all instances of a class. They are defined inside the class but outside any method.

Some examples of class attributes are:

  • Constants: You can define constants as class attributes that all instances can access.
class Point:
    DIM = 2  # Class attribute

    def __init__(self, x, y):
        self.x = x
        self.y = y
  • Default values: You can define default values as class attributes that can be overridden in instances.
class Employee:
    raise_amount = 1.05  # Class attribute

    def __init__(self, first, last, salary):
        self.first = first
        self.last = last
        self.salary = salary
  • Counters: You can use class attributes to keep track of the number of instances created.
class Dog:
    count = 0  # Class attribute

    def __init__(self, name):
        self.name = name
        Dog.count += 1

Some use cases of class attributes are:

  1. As constants to define properties of the class

  2. As default values that can be overridden in instances

  3. As counters to keep track of instances created

  4. As mutable attributes that can be modified, changing the attribute for all instances

For example:

class Dog:
    species = "mammal"  # Class attribute

    def __init__(self, name, breed):
        self.name = name 
        self.breed = breed

d1 = Dog("Fido", "Poodle")
d2 = Dog("Buddy", "Lab")

print(d1.species)  # mammal  
print(d2.species)  # mammal

Dog.species = "reptile"  

print(d1.species) # reptile  
print(d2.species) # reptile

In summary, class attributes allow you to define properties and values that are shared among all instances of a class. They are useful for defining constants, default values, counters and mutable attributes.

Hope this explanation of class attributes and their use cases helps! Let me know if you have any other questions.


Disclaim: This article was created with Rix (AI). I have asked the questions so you don't have to. I hope this helps. Comment below if you have something to say.