-
Notifications
You must be signed in to change notification settings - Fork 0
OBJECT ORIENTED PROGRAMMING
It's a programming paradigm that uses objects and classes to organize code.
Programs designed around functions are called procedure oriented programms
while those designed around classes are called object oriented.
An object is a self-contained unit that consists of both data and the methods that operate on that data.
The main concept of OOP is to bind data and the functions that work with that
data together as a single unit so that no other part of the code can access this data.
-
Class: A class is a blueprint or template for creating objects. It defines a set of attributes and methods (functions) that the objects will have. -
Object: An object is an instance of a class. It is a self-contained unit that contains both data and the methods that operate on that data. -
Attribute: A variable defined inside a class. It is used to store informaion about the object. -
Method: A method is a function that is defined inside a class. It is used to perform some action or calculation on the object. -
Inheritance: Inheritance is a mechanism that allows a class (subclass or derived class) to inherit properties and behaviors from another class (superclass or base class). This promotes code reuse and the creation of a hierarchy of classes. -
Polymorphism: It is the ability of a class to take on multiple forms. It involves the ability of a function or method to behave differently based on the context. It's achieved in Python through inheritance and method overloading. -
Encapsulation: The idea of building data and methods that operate on that data within a single unit(object). This is done to protect the data from outside access and modification. -
Data Abstraction: The process of exposing only the essential features of an object but hiding its implementation details. It's a way of simplifying complex systems. -
Method Overloading:: Method overloading allows a class to define multiple methods with the same name but different parameters. The appropriate method is selected based on the number or types of arguments. -
Method Overriding: Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. This allows a subclass to customize or extend the behavior of the inherited method.
These terminology help to differenciate between Independent functions and variables and those that belong to a class or object.
To create a class in Python, you use the class keyword followed by the name of the class and a colon. The class definition should be indented, and the methods and attributes of the class should be defined inside the class definition.(Body of the class)
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print("Woof!")The class defines a Dog class with two attributes: name and breed and a method called bark.
To create an object from a class, you use the class name followed by parentheses and any necessary arguments. For example:
dog1 = Dog("Fido", "Labrador")
dog2 = Dog("Buddy", "Poodle")This creates two Dog objects, dog1 and dog2 with attributes name and breed set to "Fido" and "Labrador" for dog1, and "Buddy" and "Poodle" for dog2.
In OOP, an attribute is avariable defined inside a class. It is used to store information about the object. There are two types of attributes.
variables that are unique to each instance(object) of a class. alsocalled object variables.
They are defined inside the constructor method (__init__) and are used to store information that is specific to each object. For example, if you have a Person class, each person object might have a unique name and age, which would be stored as instance attributes.
class Person:
def __init__(self, name, age):
self.name = name # instance attribute
self.age = age # instance attribute
p1 = Person("Alice", 30)
p2 = Person("Bob", 35)In this example, name and age are instance attributes of the Person class. The name attribute of p1 is "Alice" and the age attribute is 30, while the name attribute of p2 is "Bob" and the age attribute is 35.
also called class variables.
They are variables that are shared by all instances of a class.
They are defined outside the constructor method and are the same for all objects.
There is only one copy of the class variable and when any object makes changes to a class variable, that change will be seen by all the other instances.
An object variable with the same name as a class variable will hide the class variable.
Example:
class Car:
make = "Toyota" # class attribute
model = "Corolla" # class attribute
def __init__(self, year):
self.year = year # instance attribute
c1 = Car(2010)
c2 = Car(2015)In this example, make and model are class attributes of the Car class, and year is an instance attribute. The make and model attributes are shared by all Car objects, while the year attribute is unique to each object.
In Python, instance variables are considered public by default, which means they can be accessed and modified from outside the class.
class Person:
def __init__(self, name, age):
self.name = name # public instance variable
self.age = age # public instance variable
person = Person("John", 30)
print(person.name) # prints "John"They don't have any special prefixes or suffixes. These instance variables can be accessed or modified from outside the Person class using the dot notation.
Instance variables are variables that are associated with an instace of a class. In python you can make an instance variable private by adding a double underscore prefix to its name. This is the convention used to indicate that the instance variable should not be directly accessed or modified from outside the class.
Example:
class Person:
def __init__(self, name, age):
self.__name = name # private instance variable
self.__age = age # private instance variable
person = Person("John", 30)
print(person.__name) # this will raise an AttributeErrorThese instance variables can only be accessed or modified from within the Person class using setter and getter methods.
It's worth noting that the double underscore prefix does not actually make the instance variables completely private in Python. It only renames the instance variables to include the class name as a prefix, which makes it more difficult to access them from outside the class. However, the instance variables can still be accessed using the _classname__variablename notation.
print(person._Person__name) # prints "John"object._classname__variablename notation
Although it's possible to access private instance variables using the _classname__variablename notation, it's generally considered bad practice to do so. Private instance variables are meant to be an implementation detail of the class and should not be accessed or modified directly from outside the class.
class variables are variables that are associated with a class and are shared among all instances of the class. In Python, you can make a class variable private by adding a double underscore prefix to its name. This is a convention used to indicate that the class variable should not be directly accessed or modified from outside the class.
Example:
class Person:
# private class variable
__total_count = 0
def __init__(self, name, age):
self.name = name
self.age = age
Person.__total_count += 1
person1 = Person("John", 30)
person2 = Person("Jane", 25)
print(Person.__total_count) # this will raise an AttributeErrorIn Python, class variables are considered public by default, which means they can be accessed and modified from outside the class.
Class methods have one specific difference from ordinary functions. They have an extra first argument that has to be added to the beginning of the parameter list. But you do not give a value for this parameter when you call the method. Python will provide it.
It is a special variable that refers to the current instance of a class. It is used to access the attributes and methods of the current object from within the class. by convention it is called self but you could call it anything.
Example:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print("Woof!")The __init__ method takes three arguments: self, name and breed but when its called during the creation of an object only name and breed are provided.
dog1 = Dog("Fido", "Labrador")similarly, the bark function takes one parameter self but when calling the function, no arguments are provided.
dog1.bark() # prints "Woof!"This is because when the method is called, its automatically converted to:
Dog.bark(dog1)and
Dog.__init__(dog1, name, breed)The self argument is not specified when calling the method, but it is automatically passed to the method when it is called.
In object-oriented programming (OOP), a method is a function that is defined inside a class. It is used to perform some action or calculation on the object. There are several types of methods in OOP, including instance methods, class methods, and static methods.
These are functions that operate on a specific instance of a class.
They are defined inside the class definition and and take self as an argument which refers to the current instace of the object.
These are functions that operate on the class itself rather than on a specific instance of the class. They are defined using the @classmethod decorator and they take cls as the first argument which refers to the class itself.
These are functions that are associated with a class, but they do not operate on the class or on the instance of the class. They are defined using the @staticmethod decorator and they do not take any special arguments.
Example:
class Dog:
# class attribute
species = "Canis familiaris"
def __init__(self, name, breed):
# Instance attribute
self.name = name
self.breed = breed
# instance method
def say_hi(self):
print("Hello, my name is {} and my breed is {}".format(self.name, self.breed))
@classmethod
def get_species(cls): # class method
return cls.species
@staticmethod
def bark(): # static method
print("Woof!")N/B: You must refer to the variables and methods of the same object using the self only. This is called attribute reference.
In Python, methods are considered public by default, which means they can be called from outside the class.
In Python, you can make a method private by adding a double underscore prefix to its name. This is a convention used to indicate that the method should not be directly called from outside the class.
Private methods are meant to be an implementation detail of the class and you should use public methods to access and modify private methods.
Example:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# private method
def __increment_age(self):
self.age += 1
# public method
def birthday(self):
self.__increment_age()
person = Person("John", 30)
person.__increment_age() # this will raise an AttributeError
person.birthday()
print(person.age) # prints 31In the example above, the __increment_age method is private and can only be called from within the Person class. The birthday method is public and can be called from outside the Person class. When the birthday method is called, it calls the private __increment_age method to increment the person's age by one.
By using public methods to access and modify private methods, you can encapsulate the implementation details of the class and provide a more intuitive and user-friendly interface to the users of the class.
These are special methods in Python that are used to implement operator overloading.
They are called "magic methods" because they are not visible to the user, but they are called behind the scenes to perform certain actions.
Magic methods are defined with a double underscore prefix and a double underscore suffix, such as __init__ or __add__. They are used to define the behavior of certain operators, such as the + operator or the [] operator, when they are applied to objects of a class.
Examples:
-
__init__: This is the constructor method, which is called when an object is created(instantiated) from a class. It is used to iniialize the attributes of the object. -
__str__: This method is called when thestr()function is applied on an object of a class. This should return the string representation of the object. -
__len__: This method is called when thelen()function is applied to an object of the class. It should return the length of the object. -
__add__: This method is called when the+operaor is applied to two objects of a class. It should return the result of the addition. -
__getitem__: This method is called when the[]operator is applied to an object of a class. It should return the element at the specified index. -
__repr__: This method is called when therepr()function is applied to an object. It should return a string representation of Python code needed to rebuild the object uisingeval(). -
__setitem__: This method is called when an object is indexed and assigned a value such asobject[key] = value. It should set the value at he specified index. -
__delitem__: This method is called when an object is indexed and deleted such asdel object[key]. It should delete the value at the specified index. -
__contains__: This method is called when theinkeyword is used to check if an object is contained in another object. It should returnTrueif the value is contained in the object otherwiseFalse. -
__iter__: This method is called when an object is iterated over, such as in a for loop. It should return an iterator object. -
__del__: This method is used to clean up resources when an object is deleted. It is called automatically when an object is deleted and is used to release resources that the object was using.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"x = {self.x} and y = {self.y}"
def __repr__(self):
return "Vector({}, {})".format(self.x self.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # calls __add__ method
print(v3) # calls __str__ method
# x = 4 and y = 6
repr(v1) # calls __repr__ method
# Vector(1, 2)
v6 = eval(repr(Vector(2, 4))) # eval recreates a new object from the string produced by __repr__
print(str(v6))
# x = 2 and y = 4A setter is a method that updates the value of an instance variable.
A getter is a method that retrieves the value of an instance variable.
setters and getters are used to give you more control over how instance variables are accessed and modified.
Example:
class Person:
def __init__(self, name, age):
self.__name = name # private instance variable
self.__age = age # private instance variable
# getter method
def get_name(self):
return self.__name
# setter method
def set_name(self, name):
self.__name = name
# getter method
def get_age(self):
return self.__age
# setter method
def set_age(self, age):
self.__age = ageIn Python, you can also use decorators to define setters and getters. Here is an example of how to use decorators to define setters and getters:
class Person:
def __init__(self, name, age):
self.__name = name # private instance variable
self.__age = age # private instance variable
# getter decorator
@property
def name(self):
return self.__name
# setter decorator
@name.setter
def name(self, name):
self.__name = name
# getter decorator
@property
def age(self):
return self.__age
# setter decorator
@age.setter
def age(self, age):
self.__age = age
# create an instance of the Person class
person = Person("John", 30)
# retrieve the name using the getter decorator
name = person.name
print(name) # prints "John"
# update the name using the setter decorator
person.name = "Jane"
# retrieve the updated name using the getter decorator
name = person.name
print(name) # prints "Jane"One of the major benefits of OOP is the reuse of code and one of the ways this is achieved is through the inheritance mechanism.
Inheritance is a way to define a new class that is a modified version of an existing class.
The new class is called a subclass and the existing class is called the superclass or baseclass.
The subclass inherits characteristics from the superclass and can also have characteristics of its own.
Example:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
class Lab(Dog):
def __init__(self, name, breed, coat_color):
super().__init__(name, breed)
self.coat_color = coat_colorThe lab class is a subclass of the Dog class. It has all the attributes of a Dog as well as an additional attribute called coat_color.
The Lab class uses the super() function to call the __init__ method of the Dog class which initializes the name and breed attributes.
you can create an instance of the Lab class like this:
dog = Lab("Daisy", "Labrador", "Yellow")
print(dog.name) # Output: "Daisy"
print(dog.breed) # Output: "Labrador"
print(dog.coat_color) # Output: "Yellow"
The name and breed attributes were inherited from the Dog class.
In Python, you can use inheritance to create a hierarchy of classes. A subclass can inherit from a superclass, and a subclass of that subclass can also inherit from the original superclass. This creates a tree-like structure, with the original superclass at the root and the subclasses branching out from it.
Example:
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
class Dog(Animal):
def __init__(self, name, breed, coat_color):
super().__init__(name, species="Dog")
self.breed = breed
self.coat_color = coat_color
class Lab(Dog):
def __init__(self, name, coat_color, breed="Labrador"):
super().__init__(name, breed, coat_color)In this example, the Lab class is a subclass of the Dog class, which is itself a subclass of the Animal class. The Lab class inherits characteristics from both the Dog class and the Animal class.
dog = Lab("Daisy", "Yellow")
print(dog.name) # Output: "Daisy"
print(dog.species) # Output: "Dog"
print(dog.breed) # Output: "Labrador"
print(dog.coat_color) # Output: "Yellow" In this example, the Lab object dog has the attributes name, species, breed, and coat_color.
The name and species attributes were inherited from the Animal class, and the breed and coat_color attributes were inherited from the Dog class.
You can also override methods from the superclass in the subclass. For example, you might want to define a different implementation of the __str__ method for the Lab class:
class Lab(Dog):
def __init__(self, name, coat_color, breed="Labrador"):
super().__init__(name, breed, coat_color)
def __str__(self):
return f"{self.name} is a {self.coat_color} {self.breed}"
dog = Lab("Daisy", "Yellow")
print(dog) # Output: "Daisy is a Yellow Labrador"In Python, you can use the issubclass() function to check if a class is a subclass of another class. For example:
class Animal:
pass
class Dog(Animal):
pass
class Lab(Dog):
pass
print(issubclass(Lab, Animal)) # Output: True
print(issubclass(Lab, Dog)) # Output: True
print(issubclass(Dog, Animal)) # Output: True
print(issubclass(Animal, Dog)) # Output: FalseIn this example, the Lab class is a subclass of the Dog class, which is itself a subclass of the Animal class. Therefore, the issubclass() function returns True when called with Lab and Animal as arguments, and also when called with Lab and Dog as arguments.
You can also use the isinstance() function to check if an object is an instance of a particular class or a subclass of that class. For example:
dog = Lab("Daisy", "Yellow")
print(isinstance(dog, Lab)) # Output: True
print(isinstance(dog, Dog)) # Output: True
print(isinstance(dog, Animal)) # Output: True
print(isinstance(dog, object)) # Output: TrueIn this example, the dog object is an instance of the Lab class, which is a subclass of the Dog and Animal classes. Therefore, the isinstance() function returns True when called with dog and any of these classes as arguments.
In Python, multiple inheritance is a way to define a class that inherits characteristics from multiple superclasses.
This means that the subclass has access to all attributes and methods of all its superclasses. '
Example:
class A:
def method1(self):
print("Method 1 from class A")
class B:
def method2(self):
print("Method 2 from class B")
class C(A, B):
def method3(self):
print("Method 3 from class C")
obj = C()
obj.method1() # Output: "Method 1 from class A"
obj.method2() # Output: "Method 2 from class B"
obj.method3() # Output: "Method 3 from class C"In this example, the C class inherits from both the A and B classes. It has access to all the methods of both superclasses, as well as an additional method called method3 that is unique to the C class.
Multiple inheritance can be useful in certain situations, but it can also be complex and can lead to ambiguities if not used carefully. For example, if both superclasses have a method with the same name, it's not clear which method the subclass should inherit. To avoid these ambiguities, Python uses the C3 algorithm to determine the method resolution order (MRO) of a class, which specifies the order in which methods are inherited from multiple superclasses.
When a class inherits from multiple superclasses, it's important to define a method resolution order (MRO) to determine the order in which methods are inherited. In Python, the MRO is determined using the C3 algorithm, which ensures that a subclass always appears before its superclasses in the MRO.
Example:
class A:
def method(self):
print("Method from class A")
class B(A):
def method(self):
print("Method from class B")
class C(A):
def method(self):
print("Method from class C")
class D(B, C):
pass
obj = D()
obj.method() # Output: "Method from class B"In this example, the D class inherits from both the B and C classes, which both override the method method of the A class. The MRO of the D class is [D, B, C, A], so the method method of the B class is called, rather than the method method of the C class.
Polymorphism refers to the ability of a single function or method to behave differently based on the objects its called on.
For example, suppose you have a base class Shape and derived classes Circle, Square, and Triangle. You could define a method area in the Shape class that calculates the area of the shape, and then define the method in each of the derived classes to calculate the area of the specific shape.
Then, you could create a list of shapes and call the area method on each of them, and the method would behave differently depending on the type of shape it is called on.
Example:
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# create a list of shapes
shapes = [Circle(5), Square(10), Triangle(5, 7)]
# call the area method on each shape
for shape in shapes:
print(shape.area())Another way that polymorphism can be implemented in Python is through the use of duck typing. In Python, an object is considered "duck-typed" if it has the required methods or attributes, regardless of its actual type.
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return "Woof!"
class Cat:
def __init__(self, name):
self.name = name
def speak(self):
return "Meow!"
def make_sound(animal):
if hasattr(animal, "name") and callable(getattr(animal, "speak")):
print(f"{animal.name} says {animal.speak()}")
else:
raise TypeError("Invalid animal object")
dog = Dog("Buddy")
cat = Cat("Fluffy")
make_sound(dog)
make_sound(cat)output:
Buddy says Woof!
Fluffy says Meow!In this example, the make_sound function expects an object that has a name attribute and a speak method. It uses the hasattr function to check if the object has these attributes, and the callable function to check if the speak attribute is a method. If both of these checks pass, it prints out a message with the name of the animal and the sound it makes.