Object Oriented Programming Python
Object Oriented Programming Python
Inheritence
Polymorphism
Encapsulation
Class
A class is the particular object type created by executing a class statement. Class objects
are used as templates to create instance objects, which embodies the attributes: the “data
attributes” and “methods”, specific to a data type. A class definition is given below:
class ClassName:
<statement-1>
.
.
<statement-N>
The following example gives a glimpse of how a class is defined.
Method
A method is a function that belongs to an object. In Python, the term “method” is not
unique to class instance, other object types can have methods as well. For example, list
objects have methods, namely, append, insert, remove, sort, and so on.
Usually in a class, the method is defined inside its body. If called as an attribute of an
instance of that class, the method will get the instance object as its first argument (which
is usually called self). Self is merely a conventional name for the first argument of a
method. For example, a method defined as meth (self,a,b,c) should be called as x .meth
(a, b, c) for an instance x of the class in which the definition occurs; the called method
will think it is called as meth (x, a, b, c). The idea of self was borrowed from “Modula-3”
programming language.
It is not necessary that the function definition is textually enclosed in the class definition;
assigning a function object to a local variable in the class is also fine. For example:
>>> def f1(self, x, y) :
... return min(x, x+y)
...
>>> class test_class:
. . . aa=f1
... def bb(self) :
... return 'hello world'
... cc=bb
>>>
Here aa, bb and cc are all attributes of class test_class that refer to function objects, and
consequently, they are all methods of instances of class test_class; cc being exactly
equivalent to bb. Note that this practice usually confuses the reader of the program.
Usually, Python use methods for some functionality (e.g. list. index ()), but functions for
other (e.g. len (list)). The major reason is history; functions were used for those
operations that were generic for a group of types and which were intended to work even
for objects that did not have methods at all (e.g. tuples). In fact, implementing len (), max
(), min () etc. as built-in functions has actually less code than implementing them as
methods for each type. One can quibble about individual cases, but it is part of Python,
and it is too late to make such fundamental changes now. The functions have to remain to
avoid massive code breakage.
Class object
When a class definition is created, a “class object” is also created. The class object is
basically a wrapper around the contents of the namespace created by the class definition.
Class object support two kinds of operations: “attribute reference” and “instantiation”.
Attribute reference
Attribute references use the standard syntax used for all attribute references in Python:
obj .name. Valid attribute names are all the names that were in the class’s namespace,
when the class object was created. So, if the class definition looked like this:
>>> type(MyClass)
<type 'classobj'>
From the above expression, it can be noticed that MyClass is a class object.
Instantiation
A class object can be called to yield a class instance. Class instantiation uses function
notation. For example (assuming the above class MyClass):
x=MyClass()
creates a new instance of the class MyClass, and assigns this object to the local variable
x.
def _init_(self) :
self.data=[ ]
When a class defines an _init_() method, class instantiation automatically invokes
_init_() for the newly created class instance. So in this example, a new, initialized
instance can be obtained by:
x=MyClass()
Of course, the _init_() method may have arguments for greater flexibility. In that case,
arguments given to the class instantiation operator are passed on to _init_(). For example,
Data attribute correspond to variable of a class instance. Data attributes need not be
declared; like local variables, they spring into existence when they are first assigned to.
For example, if x is the instance of MyClass (created before), the following piece of code
will print the value 16, without leaving a trace:
>>> x.counter=1
>>> while x.counter<10:
... x.counter=x.counter*2
>>> print x.counter
16
>>> del x.counter
The other kind of instance attribute reference is a method. Any function object that is a
class attribute defines a method for instances of that class. So, x. f is a valid method
reference, since MyClass’. f is a function.
Method object
In the MyClass example, x . f is a method object, and x . f () returns the string ‘ hello
world ‘. The call x. f () is exactly equivalent to MyClass . f (x). In general, calling a
method with a list of n arguments is equivalent to calling the corresponding function with
an argument list that is created by inserting the method’s object before the first argument
>>> x. f ()
'hello world'
>>> x.f()==MyClass.f(x)
True
The method object can be stored and can be called later.
>>> xf=x.f
>>> print xf()
hello world
>>> type(xf)
<type 'instancemethod'>
Pre-defined attributes
Class and class instance objects has some pre-defined attributes:
Class object
Some pre-defined attributes of class object are:
_name_
This attribute give the class name.
>>> MyClass._dict_
{ ' i ' : 12345, ' _module_ ': ' _main_ ', ' _doc_ ': 'A simple example
class', 'f': <function f at 0x0640A070>}
_bases_
This attribute give the tuple (possibly empty or a singleton) containing the base classes.
_dict_
This give attribute dictionary of class instance.
>>> x. dict
{}
_class_
This give the instance’s class.
>>> x._class_
<class_main_.MyClass at 0x063DA880>
Customizing attribute access
The following are some methods that can be defined to customize the meaning of
attribute access for class instance.
class Person:
"""The program handles individual's data"""
population=0
def _init_ (self,Name,Age):
"""Initializes the data."""
self.name=Name
self.age=Age
Person.population+=1
def _del_(self) :
"""Deleting the data."""
print('Record of {0} is being removed'.format(self.name))
Person.population-=1
def AgeDetails(self):
'''Age details:'''
print('{0} is {1} years old'.format(self.name,self.age))
def Records(els):
"""Print number of records."""
print('There are {0} recordsformat(els.population))
records=classmethod(Records)
print Person. _doc_
record1=Person('Ram', 26)
print Person.AgeDetails. _doc_
record1.AgeDetails()
Person.records()
record2=Person('Ahmed' , 20)
print record2.AgeDetails. _doc_
record2.AgeDetails()
record2.records()
record3=Person('John',22)
print Person.AgeDetails. _doc_
record3.AgeDetails()
Person.records()
del record1,record2
Person.records()
The output is:
The _doc_ attribute is used to access docstrings of class (Person. _doc_ ) and methods
(record2 . AgeDetails . doc ).
The _del_() method is called when an instance is about to be destroyed. This is also
called a destructor.
Inheritence
A class can be based on one or more other classes, called its “base class(es)”. It then
inherits the data attributes and methods of its base classes. This is called “inheritance”,
and the class which inherits from the base class is called “derived class”. A class
definition first evaluates the inheritance list, if present. A simple form of derived class
definition looks like:
class DerivedClassName(BaseClassName):
<s.tatement-1>
.
.
.
<statement-N>
In place of a base class name BaseClassName, other arbitrary expressions are also
allowed. This is useful, for example, when the base class is defined in another module:
class DerivedClassName(modname.BaseClassName):
The following example demonstrates class inheritance.
Derived class
Derived class method
Base class method
Parent attribute : 200
Execution of a derived class definition proceeds in the same way as for a base class. If a
requested attribute is not found in the class, the search proceeds to look in the base class.
This rule is applied recursively, if the base class itself is derived from some other class.
The following example is a step further in understanding inheritance.
class Person:
population=0
def _init_ (self,Name,Age):
self.name=Name
self.age=Age
Person.population+=1
def Record(self):
print('Name:"{0}" Age:"{1}"'.format(self.name,self.age))
class Employee(Person):
def _init_ (self,Name,Age, Salary) :
Person. _init_ (self,Name,Age)
self.salary=Salary
print('Entered record for {0format(self.name))
def Record(self):
Person.Record(self)
print('Salary: "{0:d}"'.format(self.salary))
class Employer(Person):
def _init_ (self,Name,Age,Percentage):
Person. _init_(self,Name, Age)
self.percentage=Percentage
print('Entered record for {0format(self.name))
def Record(self):
Person.Record(self)
print('Partnership percent: "{0:d}format(self.percentage))
employee1=Employee('Ram',26,25000)
employee2=Employee('Ahmed',20,50000)
employee3=Employee('John', 22,75000)
employer1=Employer('Michael',58,60)
employer2=Employer('Kishan',52,40)
members= [employee1, employee2, employee3, employer1, employer2]
for member in members:
member.Record()
The output is:
# InheritenceExample2.py
class Parent: # Base class definition
def printInfo(self):
print 'Base class method'
def parentMethod(self):
self.printInfo()
class Child(Parent): # Derived class definition
def printlnfo(self):
print 'Derived class method'
c=Child()
c.parentMethod()
The output is:
Name mangling
In Python, there is a mechanism called “name mangling” to avoid name clashes of names
in class with
names defined by sub-classes. Any identifier of the form spam (at least two leading
underscores, at
most one trailing underscore) is textually replaced with _classname. spam, where
classname is
the current class name. Note that, the mangling rule is designed mostly to avoid
accidents.
# InheritenceExample3.py
class Parent: # Base class definition
def _printlnfo(self):
print 'Base class method'
def parentMethod(self):
self. _printlnfo()
class Child(Parent): # Derived class definition
def _printlnfo(self):
print 'Derived class method'
c=Child()
print Parent._diet_.keys()
print Child. _dict_ .keys()
c.parentMethod()
c._Child_printlnfo()
c._Parent_printlnfo()
The output is:
class A(object):
def printInfo ( self) :
print 'Class A'
class B(A):
def printInfo(self):
print 'Class B'
# super(B,self).printInfo()
A.printInfo(self)
class C(A):
def printlnfo(self):
print 'Class C'
. super(C,self).printlnfo()
class D(B,C):
def printInfo (self) :
print 'Class D'
super(D,self).printlnfo()
foo=D ( )
foo.printInfo ()
Running the code yield the following output.
Class D
Class B
Class A
It can be observed that C class printInfo() is skipped. The reason for that is because B
class printlnfoO calls A class printlnfoO directly. The purpose of super () is to entertain
method resolution order. Now un-comment super(B,self) .printlnfoO and comment-out
A. print Inf o (self). The code now yields a different result.
Class D
Class B
Class C
Class A
Now all the printlnfo() methods get called. Notice that at the time of defining B .printlnfo
(), one can think that super (B, self) .printlnfoO is the same as calling A.printlnfo (self),
however, this is wrong. In the above situation,
super (B, self) .printlnfoO actually calls C . print Inf o ( self).