Python - OOPS Concept - Inshorts View

|
Divnesh BLOG
OOPS Concepts in Python
This style of programming where we create a template and create copies from that template is called object oriented programming. This style allows us to code for scenarios closely linked with real life.
The template we create is called a Class and the copies we create out of it is called an object.
Objects are real world entities. Anything you can describe in this world is an object. Classes on the other hand are not real. They are just a concept. Class is a short form of Classification. A class is a classification of certain objects and it is just a description of the properties and behavior all objects of that classification should possess.
Built in class:
Like any programming language, python also has built-in classes. Some of the built-in classes which you have already used are:
  • List
  • Date
  • Tuple
  • Set
To create an object, we need a class. The syntax for creating an object is "()", where is the name of the class.
Eg:
Mobile()
Mobile()
Mobile()
Object Literals:
Object literal is a special syntax through with objects are created and initialized.
For example,
  • list1 = [1,2,3]
  • dict1 = {"name": "Gopal", "age": 32}
  • tup1 = (1,2,3)
All objects have an internal unique id ( just like aadhar or green card number ). We can check this using the inbuilt id(). The below code will display the unique number associated with the object.
     class Mobile:
         pass
         mob1=Mobile()
        mob2=Mobile()  
        print("Id for mobile 1 is :", id(mob1))
         print("Id for mobile 2 is :", id(mob2))
To create attributes and values for those attributes? This can be done by using the . (dot) operator.
reference_variable.attribute_name=value.
class Mobile:
    pass
mob1=Mobile()
mob2=Mobile()
mob1.price=20000
mob1.brand="Apple"

mob2.price=3000
mob2.brand="Samsung"

Variable
Attribute
Variable_name = value creates the variable and assigns the value if the variable does not exist already.
For example:
x = 5
reference_variable.attribute_name = value creates the attribute and assigns the value if the attribute does not exist already.
For example: 
m1.color = "Green"
Variable_name = value updates the the value if the variable exists already.
For example: 
x = 5,x = 6
reference_variable.attribute_name = value updates the attribute if the attribute exists already.
For example:  
m1.color = "Green", m1.color = "Red"
Accessing a non-existent variable throws an error
Accessing a non-existent attribute throws an error
A variable can be assigned to another variable.
For example:
y = x
The value of an attribute can be assigned to another variable.
For example:
c1 = m1.color

Attributes can be added to a class through a special function called __init__(). 
We can create behavior in a class by adding functions in a class. However, such functions should have a special parameter called self as the first parameter.
class Mobile:
    def __init__(self, brand, price):
        print("Inside constructor")
        self.brand = brand
        self.price = price
 
    def purchase(self):
        print("Purchasing a mobile")
        print("This mobile has brand", self.brand, "and price", self.price)
 
print("Mobile-1")
mob1=Mobile("Apple", 20000)
mob1.purchase()
 
print("Mobile-2")
mob2=Mobile("Samsung",3000)
mob2.purchase()
In python, everything is an object. Thus everything would have either attributes or behavior or both. That means even numbers, strings, list, set, dictionary, etc are all treated as objects in python.
Example
Description
(12.5).is_integer()
Here we are invoking a method is_integer() on a numerical value. That means numerical values are all objects
"hello".upper()
Here we are invoking upper() method on a string object.
[1,2,3].reverse()
Here we are invoking the reverse method on a list object

Function
Method
Is a block of code with a name
Is part of an object and represents the behavior of the object
It can be invoked using the name of the function and passing parameters

Example: len([1,2,3])
Can be invoked only on an object, using dot operator. Without an object we cannot invoke a method

Example: [1,2,3].reverse()
Parameters are optional in a function
A method must have at least one parameter : self

Abstraction
When we invoke the purchase() on a mobile object, we don’t have to know the details of the method to invoke it. We don’t have to know how the reverse() method is working in order to use it in our list.
This ability to use something without having to know the details of how it is working is called as abstraction.
SUMMARY:
  • OOP is a style of programming which allows us to club data and behavior together.
  • This is more suited for coding real life scenarios.
  • Objects are real world entities
  • Class is just a classification. It is just a concept.
  • Class is a description of attributes and behavior that objects of that classification should possess.
  • Attributes are created in a special function called __init__ and behaviors are created using functions called methods.
  • Objects can be created using ClassName() or using object literals for some of the built in classes
  • Attributes are created using reference_variable.attribute_name = value syntax.
  • Behavior is created by defining a function inside the class having a special parameter called self.
In build functions:
we can use the inbuilt special __str__ method. This method MUST return a string and this string will be used when the object is printed. This is useful in debugging as we can print the values of the attributes
class Shoe:
    def __init__(self, price, material):
        self.price = price
        self.material = material
 
    def __str__(self):
       return "Shoe with price: " + str(self.price) + " and material: " + self.material
 
s1=Shoe(1000, "Canvas")
print(s1)
Output: Shoe with price: 1000 and material: Canvas
Class diagram
 A class diagram four parts: the name of the class, the list of attributes, the list of methods and access specifiers.
In a class diagram, a – sign indicates private access and + indicates public access

Note: We can create private methods by adding a double underscore in front of it, just like private variables. Also, if a method has both leading and trailing double underscores ( like __init__, __str__, etc) it indicates that it is a special built-in method. As per coding convention, we are not supposed to create our own methods with both leading and trailing underscores.
Coding Standards
All variable names and method names are in snake_case and all class names should be in PascalCase ( It is similar to camelCase but the first character is also Capitalized ). For example:

Classes:
Mobile
RegularCustomer

Methods:
change_password()
display_details()

Variables:
price = 1000
brand = "Samsung"
Summary:
  • Reference variables hold the objects
  • An object can have multiple reference variables
  • Assigning a new reference variable to an existing object does not create a new object
  • We can create objects without reference variable as well
  • Class diagrams represent the class with its attributes and behavior
class Mobile:
    def __init__(self, brand, price):
        print("Inside the Mobile constructor")
        self.brand = brand
        self.price = price
        self.total_price = None
 
    def purchase(self):
        if self.brand == "Apple":
            discount = 10
        else:
            discount = 5
        self.total_price = self.price - self.price * discount / 100
        print("Total price of", self.brand, "mobile is", self.total_price)
 
    def return_product(self):
        print("Refund Amount for", self.brand, "mobile is", self.total_price)
 
class Shoe:
    def __init__(self, material, price):
        print("Inside the Shoe constructor")
        self.material = material
        self.price = price
        self.total_price = None
 
    def purchase(self):
        if self.material == "leather":
            tax = 5
        else:
            tax = 2
        self.total_price = self.price + self.price * tax / 100
        print("Total price of", self.material, "shoe is", self.total_price)
 
    def return_product(self):
        print("Refund Amount for", self.material, "shoe is", self.total_price)
 
mob1=Mobile("Apple", 20000)
mob2=Mobile("Samsung", 10000)
 
shoe1=Shoe("leather",3000)
shoe2=Shoe("canvas",200)
 
mob1.purchase()
mob2.purchase()
 
shoe1.purchase()
shoe2.purchase()
 
mob2.return_product()
 
shoe1.return_product()
We can put a lock on that data by adding a double underscore in front of it,
class Customer:
    def __init__(self, cust_id, name, age, wallet_balance):
        self.cust_id = cust_id
        self.name = name
        self.age = age
        self.__wallet_balance = wallet_balance
 
    def update_balance(self, amount):
        if amount < 1000 and amount > 0:
            self.__wallet_balance += amount
 
    def show_balance(self):
        print ("The balance is ",self.__wallet_balance)
 
c1=Customer(100, "Gopal", 24, 1000)
print(c1.__wallet_balance)
Adding a double underscore makes the attribute a private attribute. Private attributes are those which are accessible only inside the class. This method of restricting access to our data is called encapsulation
When we put a double underscore in front of the attribute name, python will internally change its name to _Classname__attribute
This is why we get an error when we try to access a private attribute.
All setter methods must accept the value to be updated as a parameter and all getter methods must not have any parameter and they must return the value.
Setter methods are called as mutator methods ( as they change or mutate the value ) and the getter methods are called accessor methods ( as they access the values )
class Customer:
    def __init__(self, id, name, age, wallet_balance):
        self.id = id
        self.name = name
        self.age = age
        self.__wallet_balance = wallet_balance
 
    def set_wallet_balance(self, amount):
        if amount < 1000 and amount>  0:
            self.__wallet_balance = amount
 
    def get_wallet_balance(self):
        return self.__wallet_balance
 
c1=Customer(100, "Gopal", 24, 1000)
c1.set_wallet_balance(120)
print(c1.get_wallet_balance())
mob2.return_product() can also be invoked as Mobile.return_product(mob2)
Thus self now refers to mob2, as this is actually pass by reference. For simplicity sake and for better readability we use mob2.return_product() instead of Mobile.return_product(mob2)
class Mobile:
    def __init__(self,price,brand):
        print (id(self))
        self.price = price
        self.brand = brand
 
    def return_product(self):
        print (id(self))
        print ("Brand being returned is ",self.brand," and price is ",self.price)
 
mob1 = Mobile(1000, "Apple")
print ("Mobile 1 has id", id(mob1))
 
mob2=Mobile(2000, "Samsung")
print ("Mobile 2 has id", id(mob2))
 
mob2.return_product()
Mobile.return_product(mob2)


Method invocation
Method definition
Explanation
mob1.display()
def display(self):       print(self.discount)
Here, 'self' is the first parameter. Hence it refers to mob1.
mob1.display()
def display(mob_obj):       print(mob_obj.discount)
Here, ‘mob_obj' is the first parameter. Hence it refers to ‘mob1'.
mob1.purchase(2)  
def purchase(self,qty):         print("Total is ", self.price*qty)  
Here, 'self' is the first parameter. Hence it refers to ‘mob1'.The second parameter is ‘qty' which stores 2 passed during invocation.
mob1.purchase(2)
def purchase(qty,self):      print("Total is ", qty.price*self)
Here, ‘qty' is the first parameter. Hence it refers to ‘mob1'. The second parameter is 'self' which stores 2 passed during invocation.
mob1.display()
def display():       print(self.discount)
This is an error, since the first parameter of a method is always a reference to the object used. Hence it should have AT LEAST one parameter.
Collection of Objects:
We can also store a number of objects inside a list or a dictionary. The below example, we have a list of mobile objects and we are iterating over the list and printing the values
class Mobile:
    def __init__(self, brand, price):
        self.brand = brand
        self.price = price
 
mob1=Mobile("Apple", 1000)
mob2=Mobile("Samsung", 2000)
mob3=Mobile("Apple", 3000)
mob4=Mobile("Samsung", 4000)
mob5=Mobile("Apple", 5000)
 
list_of_mobiles=[mob1, mob2, mob3, mob4, mob5]
 
for mobile in list_of_mobiles:
    print (mobile.brand,mobile.price)
Dictionary of Object
class Mobile:
def __init__(self,brand,price):
        self.brand = brand
        self.price = price
 
mob1=Mobile("Apple", 1000)
mob2=Mobile("Samsung", 5000)
mob3=Mobile("Apple", 3000)
 
mob_dict={
          "m1":mob1,
          "m2":mob2,
          "m3":mob3
          }
 
for key,value in mob_dict.items():
    if value.p
rice > 3000:
        print (value.brand,value.price)
Summary:
  • Everything is treated as an object in python
  • We can use objects inside collections
  • Encapsulation is preventing access to a data outside the class
  • Adding a __ in front of a attribute makes it private
  • In python, adding a __ changes the name of the attribute to _Classname__attribute
We can create shared attributes by placing them directly inside the class and not inside the constructor. And since this attribute is not owned by any one object, we don’t need the self to create this attribute. Such variables which are created at a class level are called static variables
Since static variable is object independent, we need a way to access the getter setter methods without an object. This is possible by creating static methods. Static methods are those methods which can be accessed without an object. They are accessed using the class name.
There are two rules in creating such static methods:
  • The methods should not have self
  • @staticmethod must be written on top of it
@staticmethod
def get_discount():
    return Mobile.__discount
@staticmethod
def set_discount(discount):
    Mobile.__discount=discount
Complete Solution:
·        

class Mobile:
    __discount = 50
    def __init__(self, price, brand):
        self.price = price
        self.brand = brand

    def purchase(self):
        total = self.price - self.price * Mobile.__discount / 100
        print (self.brand, "mobile with price", self.price, "is available after discount at", total)

    @staticmethod
    def enable_discount():
        Mobile.set_discount(50)

    @staticmethod
    def disable_discount():
        Mobile.set_discount(0)

    @staticmethod
    def get_discount():
        return Mobile.__discount

    @staticmethod
    def set_discount(discount):
        Mobile.__discount = discount

mob1=Mobile(20000, "Apple")
mob2=Mobile(30000, "Apple")
mob3=Mobile(5000, "Samsung")

Mobile.disable_discount()

mob1.purchase()

Mobile.enable_discount()

mob2.purchase()

Mobile.disable_discount()

mob3.purchase()
Summary:
  • Static attributes are created at class level.
  • Static attributes are accessed using ClassName.
  • Static attributes are object independent. We can access them without creating instance (object) of the class in which they are defined.
  • The value stored in static attribute is shared between all instances(objects) of the class in which the static attribute is defined.
Relationship
Relationship
Description
Example
Inheritance
When one object is a type of another object
Mobile is a Product
Aggregation
When one object owns another object, but they both have independent life cycle
Customer has an Address. Even if the Customer is no more, there may be other customers in that address. So Address continues to exist even after a customer is no more
Composition
When one object owns another object, but they both have same life cycle
College has a department. If the college closes, the department is also closed
AGGREGATION:
ust like Customer "has-a" name, Customer "has-a" age, Customer "has-a" phone_no, now Customer also "has-a" Address

add1=Address(123,"5th Lane",56001)
add2=Address(567,"6th Lane",82006)

cus1=Customer("Jack",24,1234,add1)
cus2=Customer("Jane",25,5678,add2)

Summary:
  • Classes can have relationships with other classes
  • In aggregation one class owns another though they have their own life cycle
  • Aggregation is represented using an diamond symbol in the class diagram
Inheritance:
 FeaturePhone is inheriting the Phone and SmartPhone is inheriting the Phone class (SmartPhone "is-A" phone, FeaturePhone "is-A" phone). So Phone is the parent class and FeaturePhone and SmartPhone are derived classes.
Advantages of inheritance:
We can keep common properties in a single place. Thus any changes needs to be made need not be repeated.
Inheritance encourages code reuse thus saving us time.
If we want to add a new type of phone later on, we can simply inherit the Phone class instead of writing it from scratch.
When we have a inheritance relationship, the attributes and behaviors get inherited, just like a child inherits certain attributes and behaviours from its parent.
From a code perspective, a child class inherits:
Constructor
Non Private Attributes
Non Private Methods
Method Overriding
When the child has a method with the same name as that of the parent, it is said to override the parent’s method. This is called as Method Overriding. Method overriding is also called as Polymorphism.
class Phone:
    def __init__(self, price, brand, camera):
        print ("Inside phone constructor")
        self.__price = price
        self.brand = brand
        self.camera = camera
 
    def buy(self):
        print ("Buying a phone")
 
    def return_phone(self):
        print ("Returning a phone")
 
class FeaturePhone(Phone):
    pass
 
class SmartPhone(Phone):
    def buy(self):
        print ("Buying a smartphone")
 
s=SmartPhone(20000, "Apple", 13)
 
s.buy()
Even though the child class may override the methods of the parent class, it might still decide to use the parent class overridden method. To invoke anything belonging to the parent class, the child class needs to use the super() function
class Phone:
    def __init__(self, price, brand, camera):
        print ("Inside phone constructor")
        self.__price = price
        self.brand = brand
        self.camera = camera
 
    def buy(self):
        print ("Buying a phone")
 
    def return_phone(self):
        print ("Returning a phone")
 
class FeaturePhone(Phone):
    pass
 
class SmartPhone(Phone):
    def buy(self):
        print ("Buying a smartphone")
        super().buy()
 
s=SmartPhone(20000, "Apple", 13)
 
s.buy()
To access the parent class constructor we can use super(). Thus, the data is passed to the child class constructor, from there the data is sent to the parent class constructor and thus the attributes of the parent class get inherited.
super() function can be used to access the constructor or methods of the parent class, but not the attributes. Also super() function can be used only inside a class and not outside it
Summary:
A class can inherit from another class.
Inheritance improves code reuse
Constructor, attributes, methods get inherited to the child class
The parent has no access to the child class
Private properties of parent are not accessible directly in child class
Child class can override the attributes or methods. This is called method overriding
super() is an inbuilt function which is used to invoke the parent class methods and constructor
class Product:
    def review(self):
        print ("Product customer review")
 
class Phone(Product):
    def __init__(self, price, brand, camera):
        print ("Inside phone constructor")
        self.__price = price
        self.brand = brand
        self.camera = camera
 
    def buy(self):
        print ("Buying a phone")
 
    def return_phone(self):
        print ("Returning a phone")
 
class SmartPhone(Phone):
    pass
 
s=SmartPhone(20000, "Apple", 12)
 
s.buy()
s.review()

class Phone:
    def __init__(self, price, brand, camera):
        print ("Inside phone constructor")
        self.__price = price
        self.brand = brand
        self.camera = camera
 
    def buy(self):
        print ("Buying a phone")
 
    def return_phone(self):
        print ("Returning a phone")
 
class SmartPhone(Phone):
    pass
 
class FeaturePhone(Phone):
    pass
 
SmartPhone(1000,"Apple","13px").buy()

class Phone:
    def __init__(self, price, brand, camera):
        print ("Inside phone constructor")
        self.__price = price
        self.brand = brand
        self.camera = camera
 
    def buy(self):
        print ("Buying a phone")
 
    def return_phone(self):
        print ("Returning a phone")
 
class Product:
    def review(self):
        print ("Customer review")
 
class SmartPhone(Phone, Product):
    pass
 
s=SmartPhone(20000, "Apple", 12)
 
s.buy()
s.review()
If we programmatically declare our return_policy() of Product class as an abstract method, then every sub-class of Product class MUST override the abstract method. 
from abc import ABCMeta, abstractmethod
class Product(metaclass=ABCMeta):
    @abstractmethod
    def return_policy(self):
        pass
Note:
Usually the parent class is an abstract class.
An abstract class should not be instantiated.
An abstract class may contain 0 or many abstract methods
Abstract classes are meant to be inherited.
The child class must implement/override all the abstract methods of the parent class. Else the child class cannot be instantiated. i.e., An abstract method must be overridden in child class. If not, child class is considered as abstract.
Exception:
class CreditCard:
    def __init__(self,card_no,balance):
        self.card_no=card_no
        self.balance=balance

class Customer:
    def __init__(self,cards):
        self.cards=cards

    def purchase_item(self, price, card_no):
        if price < 0:
            raise Exception("Invalid Price")   
        if card_no not in self.cards:
            raise Exception("Wrong card")
        if price>self.cards[card_no].balance:
            raise Exception("Wrong card")

card1=CreditCard(101,800)
card2=CreditCard(102,2000)
cards={card1.card_no:card1,card2.card_no:card2}
c=Customer(cards)

while(True):
    card_no=int(input("Please enter a card number"))
    try:
        c.purchase_item(1200,card_no)
        break
    except Exception as e:
        print("Something went wrong. "+str(e))
Notes:
Custom exceptions are created by inheriting the Exception class
Custom classes give greater flexibility in handling exceptions
The parent class exception must come after the child class exceptions in the except clause
    



Featured Post

HTML cheetsheet

List: Link tgs Dropdown

Popular Post

(C) Copyright 2018, All rights resrved InShortView. Template by colorlib