Wednesday, November 27, 2019

Revising OOP Basics


Object Oriented Programming in Python
Below is a simple Python program that creates a class with single method.
# A simple example class
class Test:
      
    # A sample method 
    def fun(self):
        print("Hello")
  
# Driver code
obj = Test()
obj.fun()

Output:
Hello
As we can see above, we create a new class using the class statement and the name of the class. This is followed by an indented block of statements which form the body of the class. In this case, we have defined a single method in the class.
Next, we create an object/instance of this class using the name of the class followed by a pair of parentheses.
The self
1.    Class methods must have an extra first parameter in method definition. We do not give a value for this parameter when we call the method, Python provides it
2.    If we have a method which takes no arguments, then we still have to have one argument – the self. See fun() in above simple example.
3.    This is similar to this pointer in C++ and this reference in Java.
When we call a method of this object as myobject.method(arg1, arg2), this is automatically converted by Python into MyClass.method(myobject, arg1, arg2) – this is all the special self is about.
The __init__ method
The __init__ method is similar to constructors in C++ and Java. It is run as soon as an object of a class is instantiated. The method is useful to do any initialization you want to do with your object.
# A Sample class with init method
class Person:
    # init method or constructor 
    def __init__(self, name):
        self.name = name
  
    # Sample Method 
    def say_hi(self):
        print('Hello, my name is', self.name)
  
p = Person('Shwetanshu')
p.say_hi()
Output:
Hello, my name is Shwetanshu
Here, we define the __init__ method as taking a parameter name (along with the usual self). .

Class and Instance Variables (Or attributes)
In Python, instance variables are variables whose value is assigned inside a constructor or method with self.
Class variables are variables whose value is assigned in class.
# Python program to show that the variables with a value 
# assigned in class declaration, are class variables and
# variables inside methods and constructors are instance
# variables.
   
# Class for Computer Science Student
class CSStudent:
  
    # Class Variable
    stream = 'cse'             
  
    # The init method or constructor
    def __init__(self, roll):
    
        # Instance Variable    
        self.roll = roll       
   
# Objects of CSStudent class
a = CSStudent(101)
b = CSStudent(102)
   
print(a.stream)  # prints "cse"
print(b.stream)  # prints "cse"
print(a.roll)    # prints 101
   
# Class variables can be accessed using class
# name also
print(CSStudent.stream) # prints "cse"    
We can define instance variables inside normal methods also.
# Python program to show that we can create 
# instance variables inside methods
   
# Class for Computer Science Student
class CSStudent:
      
    # Class Variable
    stream = 'cse'      
      
    # The init method or constructor
    def __init__(self, roll):
          
        # Instance Variable
        self.roll = roll            
  
    # Adds an instance variable 
    def setAddress(self, address):
        self.address = address
      
    # Retrieves instance variable    
    def getAddress(self):    
        return self.address   
  
# Driver Code
a = CSStudent(101)
a.setAddress("Noida, UP")
print(a.getAddress()) 
Output :
Noida, UP

How to create an empty class?
We can create an empty class using 
pass statement in Python.

# An empty class
class Test:
    pass
Data hiding
In Python, we use double underscore (Or __) before the attributes name and those attributes will not be directly visible outside.
class MyClass:
  
    # Hidden member of MyClass
    __hiddenVariable = 0
    
    # A member method that changes 
    # __hiddenVariable 
    def add(self, increment):
        self.__hiddenVariable += increment
        print (self.__hiddenVariable)
   
# Driver code
myObject = MyClass()     
myObject.add(2)
myObject.add(5)
  
# This line causes error
print (myObject.__hiddenVariable)
Output :
2
7
Traceback (most recent call last):
  File "filename.py", line 13, in
    print (myObject.__hiddenVariable)
AttributeError: MyClass instance has
no attribute '__hiddenVariable'
In the above program, we tried to access hidden variable outside the class using object and it threw an exception.
We can access the value of hidden attribute by a tricky syntax:
# A Python program to demonstrate that hidden
# members can be accessed outside a class
class MyClass:
  
    # Hidden member of MyClass
    __hiddenVariable = 10
  
# Driver code
myObject = MyClass()     
print(myObject._MyClass__hiddenVariable)
Output :
10
Private methods are accessible outside their class, just not easily accessible. Nothing in Python is truly private; internally, the names of private methods and attributes are mangled and unmangled on the fly to make them seem inaccessible by their given names [See this for source ].

Printing Objects

Printing objects gives us information about objects we are working with. In C++, we can do this by adding a friend ostream& operator << (ostream&, const Foobar&) method for the class. In Java, we use toString() method. In python this can be achieved by using __repr__ or __str__ methods.
class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = b
  
    def __repr__(self):
        return "Test a:%s b:%s" % (self.a, self.b)
  
    def __str__(self):
        return "From str method of Test: a is %s," \
              "b is %s" % (self.a, self.b)
  
# Driver Code        
t = Test(1234, 5678)
print(t) # This calls __str__()
print([t]) # This calls __repr__()
Output :
From str method of Test: a is 1234,b is 5678
[Test a:1234 b:5678]
Important Points about Printing:
·         If no __str__ method is defined, print t (or print str(t)) uses __repr__.
class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = b
  
    def __repr__(self):
        return "Test a:%s b:%s" % (self.a, self.b)
  
# Driver Code        
t = Test(1234, 5678)
print(t) 
Output :
Test a:1234 b:5678
·         If no __repr__ method is defined then the default is used.
class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = b
  
# Driver Code        
t = Test(1234, 5678)
print(t) 
Output :
<__main__.Test instance at 0x7fa079da6710>
Inheritance

One of the major advantages of Object Oriented Programming is re-use. Inheritance is one of the mechanisms to achieve the same. In inheritance, a class (usually called superclass) is inherited by another class (usually called subclass). The subclass adds some attributes to superclass.
Below is a sample Python program to show how inheritance is implemented in Python.
   
# A Python program to demonstrate inheritance 
  
# Base or Super class. Note object in bracket.
# (Generally, object is made ancestor of all classes)
# In Python 3.x "class Person" is 
# equivalent to "class Person(object)"
class Person(object):
      
    # Constructor
    def __init__(self, name):
        self.name = name
  
    # To get name
    def getName(self):
        return self.name
  
    # To check if this person is employee
    def isEmployee(self):
        return False
  
  
# Inherited or Sub class (Note Person in bracket)
class Employee(Person):
  
    # Here we return true
    def isEmployee(self):
        return True
  
# Driver code
emp = Person("Geek1")  # An Object of Person
print(emp.getName(), emp.isEmployee())
  
emp = Employee("Geek2") # An Object of Employee
print(emp.getName(), emp.isEmployee())
Output:
('Geek1', False)
('Geek2', True)

How to check if a class is subclass of another?
Python provides a function issubclass() that directly tells us if a class is subclass of another class.
# Python example to check if a class is
# subclass of another
  
class Base(object):
    pass   # Empty Class
  
class Derived(Base):
    pass   # Empty Class
  
# Driver Code
print(issubclass(Derived, Base))
print(issubclass(Base, Derived))
  
d = Derived()
b = Base()
  
# b is not an instance of Derived
print(isinstance(b, Derived))
  
# But d is an instance of Base
print(isinstance(d, Base))
Output:
True
False
False
True

What is object class?
Like Java Object class, in Python (from version 3.x), object is root of all classes.
In Python 3.x, “class Test(object)” and “class Test” are same.
In Python 2.x, “class Test(object)” creates a class with object as parent (called new style class) and “class Test” creates old style class (without object parent). Refer 
this for more details.

Does Python support Multiple Inheritance?
Unlike Java and like C++, Python supports multiple inheritance. We specify all parent classes as comma separated list in bracket.
# Python example to show working of multiple 
# inheritance
class Base1(object):
    def __init__(self):
        self.str1 = "Geek1"
        print "Base1"
  
class Base2(object):
    def __init__(self):
        self.str2 = "Geek2"        
        print "Base2"
  
class Derived(Base1, Base2):
    def __init__(self):
          
        # Calling constructors of Base1
        # and Base2 classes
        Base1.__init__(self)
        Base2.__init__(self)
        print "Derived"
          
    def printStrs(self):
        print(self.str1, self.str2)
         
  
ob = Derived()
ob.printStrs()
Output:
Base1
Base2
Derived
('Geek1', 'Geek2')

How to access parent members in a subclass?
1.    Using Parent class name
# Python example to show that base
# class members can be accessed in
# derived class using base class name
class Base(object):
  
    # Constructor
    def __init__(self, x):
        self.x = x    
  
class Derived(Base):
  
    # Constructor
    def __init__(self, x, y):
        Base.x =
        self.y = y
  
    def printXY(self):
       
       # print(self.x, self.y) will also work
       print(Base.x, self.y)
  
  
# Driver Code
d = Derived(10, 20)
d.printXY()
Output:
(10, 20)
2.    Using super()
We can also access parent class members using super.
# Python example to show that base
# class members can be accessed in
# derived class using super()
class Base(object):
  
    # Constructor
    def __init__(self, x):
        self.x = x    
  
class Derived(Base):
  
    # Constructor
    def __init__(self, x, y):
          
        ''' In Python 3.x, "super().__init__(name)"
            also works''' 
        super(Derived, self).__init__(x)
        self.y = y
  
    def printXY(self):
  
       # Note that Base.x won't work here
       # because super() is used in constructor
       print(self.x, self.y)
  
  
# Driver Code
d = Derived(10, 20)
d.printXY()
Output:
(10, 20)
Note that the above two methods are not exactly the same. In the next article on inheritance, we will covering following topics.
1) How super works? How accessing a member through super and parent class name are different?
2) How Diamond problem is handled in Python?

Exercise:
Predict the output of following Python programs

   
class X(object):
    def __init__(self, a):
        self.num = a
    def doubleup(self):
        self.num *= 2
  
class Y(X):
    def __init__(self, a):
        X.__init__(self, a)
    def tripleup(self):
        self.num *= 3
  
obj = Y(4)
print(obj.num)
  
obj.doubleup()
print(obj.num)
  
obj.tripleup()
print(obj.num)
Output:
4
8
24

# Base or Super class
class Person(object):
    def __init__(self, name):
        self.name = name
          
    def getName(self):
        return self.name
      
    def isEmployee(self):
        return False
  
# Inherited or Subclass (Note Person in bracket)
class Employee(Person):
    def __init__(self, name, eid):
  
        ''' In Python 3.0+, "super().__init__(name)"
            also works''' 
        super(Employee, self).__init__(name)
        self.empID = eid
          
    def isEmployee(self):
        return True
          
    def getID(self):
        return self.empID
  
# Driver code
emp = Employee("Geek1", "E101") 
print(emp.getName(), emp.isEmployee(), emp.getID())
Output:
('Geek1', True, 'E101')