0% found this document useful (0 votes)
7 views

Object Oriented Programming Python

Uploaded by

Sreelalitha
Copyright
© © All Rights Reserved
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Object Oriented Programming Python

Uploaded by

Sreelalitha
Copyright
© © All Rights Reserved
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 13

Object Oriented Programming

Object-oriented programming (OOP) is a programming paradigm that represents


concepts as “objects”, that have attributes which describe the object in the form of data
attributes and associated procedures known as methods. As mentioned in chapter 1,
Python is an OOP language. In Python, class form the basis of OOP. Some of the features
of OOP language are:

 Inheritence
 Polymorphism
 Encapsulation

Some of the advantages of OOP approach are:

 Reusability: A part of a code can be reused for accommodating new


functionalities with little or no changes.
 Maintenance: If some modification is made in base class, the effect gets reflected
automatically into the derived class, thus, the code maintenance is significantly less
hectic.
 Faster output: With organized and methodical coding, there is little room for
error, and as a result programmer can work comfortably, resulting in fast and
efficient output.

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:

classdef ::= "class" classname [inheritance]":"suite


inheritance ::= "(" [expression_list] ")"
classname ::= identifier
The above class definition might seem alien, it will become more clear with the progress
of this chapter. The simplest form of class definition looks like:

class ClassName:
<statement-1>
.
.
<statement-N>
The following example gives a glimpse of how a class is defined.

>>> class Output:


... def Display(self):
. . . print 'This is a class example.'
...
>>> x=Output()
>>> x . Display ()
This is a class example.
Like function definition (def statements), the class definition (Output in the above
example) must be executed before they have any effect. In practice, the statements inside
a class definition will usually be function (or more specifically “method”) definitions
(Display () in the above example), but other statements are allowed. The function
definitions inside a class normally have a peculiar form of argument list, dictated by the
calling conventions for methods (discussed later).
Creation of a class definition also creates a new namespace, and used as the local scope,
thus all assignments to local variables go into this new namespace.

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:

>>> class MyClass:


... """A simple example class"""
... i=12345
... def f(self):
... return 'hello world'
>>> MyClass.i
12345 '
>>> MyClass. _doc_
'A simple example class'
then MyClass . i and MyClass . f are valid attribute references, returning an integer and a
method object, respectively. The value of MyClass. i can also be change by assignment.
The attribute _doc_ is also a valid attribute, returning the docstring belonging to the class.

>>> 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.

>>> type(MyClass ())


<type 'instance'>
>>> type(x)
<<type 'instance's>
Many classes like to create objects with instances customized to a specific initial state.
Therefore, a class may define a special method named init (), like this:

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,

>>> class Complex:


... def _init_(self,realpart,imagpart):
... self.r=realpart
... self.i=imagpart
...
>>> x=Complex(3.0, -4.5)
>>> x.r,x.i
(3.0, -4.5)
Instance object
The only operation that done using class instance object x is attribute references. There
are two kinds of valid attribute names: “data attribute” and “method”.

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. _name_


'MyClass'
_module_
This attribute give the module name in which the class was defined.

>>> MyClass. _module_


' main '
_dict_
A class has a namespace implemented by a dictionary object. Class attribute references
are translated to lookups in this dictionary, e.g., MyClass . i is translated to
MyClass ._dict_[ “i” ].

>>> 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.

>>> MyClass. _bases_


_doc_
This attribute give the class documentation string, or None, if not defined.

>>> MyClass. _doc_


'A simple example class'
Class instance object
Some pre-defined attributes of class instance object are:

_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.

object. _getattr_ (self,name)


Called when an attribute lookup does not find the attribute name. This method should
return the (computed) attribute value or raise an AttributeError exception. Note that, if
the attribute is found through the normal mechanism, _getattr_() is not called.
>>> class HiddenMembers:
. . . def _getattr_ (self,name) :
... return "You don't get to see "+name
>>> h=HiddenMembers()
>>> h.anything
"You don't get to see anything"
object. _setattr_ (self,name,value)
Called when an attribute assignment is attempted. The name is the attribute name and
value is the
value to be assigned to it. Each class, of course, comes with a default _setattr_ , which
simply set
the value of the variable, but that can be overridden.

>>> class Unchangable:


... def _setattr_ (self,name,value):
... print "Nice try"
>>> u=Unchangable()
>>> u.x=9
Nice try
>>> u.x
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: Unchangable instance has no attribute 'x'
object. _delattr_ (self,name)
Like _setattr_ (), but for attribute deletion instead of assignment. This should only be
implemented if del ob j . name is meaningful for the object.

>>> class Permanent:


... def _delattr_ (self,name):
... print name,"cannot be deleted"
>>> p=Permanent()
>>> p.x=9
>>> del p.x
x cannot be deleted
>>> p.x
9
Class example
Till now, some basic concepts of class has been discussed. The following example
“ClassExample.py” defines a class Person, which handles name and age of multiple
individuals.

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 program handles individual's data


Age details:
Ram is 26 years old
There are 1 records
Age details:
Ahmed is 20 years old
There are 2 records
Age details:
John is 22 years old *.
There are 3 records
Record of Ram is being removed
Record of Ahmed is being removed
There are 1 records
Variables defined in the class definition are class variables (population is a class
variable); they are shared by all instances. To create instance variables (name and age are
instance variables), they can be initialized in a method, e.g. self. name=value. Both class
and instance variables are accessible through the notation self . name, and an instance
variable hides a class variable with the same name when accessed in this way. Therefore,
the class variable population is better referred as Person. population, and not self.
population. The instance variables name and age are referred as self . name and self . age,
respectively.
The Records is a method that belongs to the class and not to the instance. This is done by
using classmethod () built-in function. A class method receives the class as implicit first
argument, just like an instance method receives the instance. The class method can be
called either on the class (Person . records ()) or on an instance (record2 . records ()). The
instance is ignored except for its class.

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.

class Parent: # Base class definition


parentAttr=100
def _init_(self):
print "Base class"
def parentMethod(self):
print 'Base class method'
def setAttr(self, attr) :
Parent.parentAttr=attr
def getAttr(self):
print "Parent attribute :",Parent.parentAttr
class Child(Parent): # Derived class definition
def _init_(self):
print "Derived class"
def childMethod(self):
print 'Derived class method'
c=Child()
c.childMethod()
c.parentMethod()
c.setAttr(200)
c.getAttr()
The output is:

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:

Entered record for Ram


Entered record for Ahmed
Entered record for John
Entered record for Michael
Entered record for Kishan
Name:"Ram" Age:"26"
Salary: "25000"
Name:"Ahmed" Age:"20"
Salary: "50000"
Name:"John" Age:"22"
Salary: "75 000"
Name:"Michael" Age:"58"
Partnership percent: "60"
Name:"Kishan" Age:"52"
Partnership percent: "40"
Please note that, if a base class has an init () method, the derived class’s init ()
method, if any, must explicitly call it, to ensure proper initialization of the base class part
of the instance; for example: BaseClass. init ( self, [args . . . ] ).

New-style and classic classes


Classes and instances come in two flavors: old-style (or classic) and new-style. New-style
classes were introduced in Python 2.2. For compatibility reasons, classes are still old-
style by default. Any class which inherits from object is a new-style class. The object
class is a base for all new style classes. The following is an example showing simple
definitions of old-style and new-style classes.

>>> class ClassicExample:


... def _init_ (self):
... pass
>>> class NewStyleExample(object):
... def _init_ (self):
... pass
>>>
Overriding Methods
Derived classes may override methods of their base classes. A method of a base class that
calls another method defined in the same base class may end up calling a method of a
derived class that overrides it.

# 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:

Derived class method


It can be seen that print Inf o () of derived class is called instead of base class.
Super() function
The is a built-in function super (), which can be used for accessing inherited methods that
have been overridden in a class. The super () only works for new-style classes; in a class
hierarchy with single inheritance, super can be used to refer to parent classes without
naming them explicitly, thus making the code more maintainable.

class Parent(object): # Base class definition


def printlnfo(self):
print 'Base class method'
def parentMethod(self) :
self.printInfo()
class Child(Parent): # Derived class definition
def printInfo(self):
super(Child,self).printlnfo()
# Parent.printInfo(self)
print 'Derived class method'
c=Child()
c.parentMethod()
The output is:

Base class method


Derived class method
In the above example, to access the printlnfo () method of Parent class, super () method in
the form of super (Child, self) .printlnfo () is used, where the name of base class is not
mentioned. The other way would have been by using Parent .printlnfo (self).

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:

[' _module_ ', '_Parent _printlnfo', ' _doc_ 'parentMethod']


[' _module_ ' _doc_', '_Child_printlnfo']
Base class method
Derived class method
Base class method
Multiple inheritence
Till now, the discussion was about inheriting from one class; this is called “single
inheritance”. Python also supports “multiple inheritance”, where a class can inherit from
more than one class. A simple class definition inheriting from multiple base classes looks
like:

class DerivedClassName(Base1, Base2, Base3):


<statement-1>
.
.
.
<statement-N>
Whenever there is a call via DerivedClassName class instance, Python has to look-up the
possible
function in the class hierarchy for Basel, Base2 , Base3 , but it needs to do this in a
consistent
order. To do this, Python uses an approach called “method resolution order” (MRO)
using an algorithm called “C3” to get it straight.
Consider the following multiple inheritance example.

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).

You might also like