Tags

,

Program 1

class c:                   # class defined
    def setname(self,who): # method for class
        self.name = who

I = c()                    # instance creation
I.setname('mayank')
print I.name

We haven’t used __init__ above at all. By doing so, we have a chance to understand better the purpose of __init__ in our code .
‘.’ notation is important here. We have already been using ‘.’ notation to access the attribute of object. Recall how we have used object.method()  extensively in Python. This object.attribute expression is at the heart of OOP.

Now look at the following program: –

Program 2

class c:
    def __init__(self,who):
        self.name = who       

I =  c('mayank')
print I.name

__init__ method is run automatically and as soon as we create an instance of a class. In the first example, we have explicitly called setname method while in the second example we actually didn’t call __init__ method explicitly.

Now a word about self.name = who. Here self refer to instance in question. name is the attribute of self. By setname (or __init__) method, we are effectively mapping the name attribute of  self to who variable which was passed while defining setname (or __init__) method. Using __init__ also allows us to pass parameters as soon as instance is created. Compare above two program again. Now try running following modified version of first example as shown below –

Program 3

class c:
    def setname(self,who):
        self.name = who
        print self.name    

I = c()
print I.name
I.setname('mayank')

Here we are executing print I.name before I.setname('mayank'). If we run this program, an error is thrown: – "AttributeError: c instance has no attribute 'name'"

As I said above, __init__ method is run automatically. To illustrate this one more time, run following program.

Program 4

class Person:
    def __init__(self, name):
        self.name = name
        print self.name

    def sayhello(self):
        print 'Hi', self.name

A = Person('Sam')

A.sayhello()

This will output:-
Sam
Hi Sam

Observe that we only called the method sayhello explicitly. When we ran this program, method __init__ ran automatically.

One more point. Above we applied method on instance A like this: A.sayhello(). We can also write Person.sayhello(A, 'sam'). In former case, we are using method while in the other case we are using function notation. Observe how function is defined within a class and that self is first parameter passed to this function. So,
A.sayhello() is equivalent to Person.sayhello(A, 'Sam')


Run following program and see what is happening –

Program 5

class A:
    i = 123
    def __init__(self):
        self.i = 1234

    def f(self):
        print 'Hi'

print A.i
print A().i
A().f()

It will output –

123
1234
Hi

One more program –

Program 6


class Employee:

   empCount = 0

   def __init__(self):
      Employee.empCount += 1

emp1 = Employee()
emp2 = Employee()

print Employee.empCount

Consider following program –

Program 7

class customer:
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance

    def deposit(self, amount):
        #self.amount = amount
        self.balance = self.balance+amount
        print self.balance

    def test(self):
        print amount
        #print self.amount

client1 = customer('sam', 1000)
client2 = customer('peter', 3000)

print client1.name, client1.balance
client1.deposit(500)
client2.deposit(600)
client1.test()

Output will be –

sam 1000
1500
3600

Traceback (most recent call last):
  File "C:\Python27\Python resources\Programs\OOP\example.py", line 22, in <module>
    client1.test()
  File "C:\Python27\Python resources\Programs\OOP\example.py", line 13, in test
    print amount
NameError: global name 'amount' is not defined

So what is going on here? See deposit method. First consider self.balance. Though it first appear in __init__ method,we have access to this anywhere in the customer class and that is why we have been able to access in deposit method.
But that is not the case with amount in deposit method.  It can only be accessed by deposit. This is local to deposit and can’t be accessed by any other method and that is why the error is thrown. Here self.balance is instance variable and amount is local variable. Run the program again by uncommenting the code lines and deleting the code line print amount in test method.


Data Hiding

Let us modify Program 6 as shown below: –

Program 8


class Employee:

   __empCount = 0

   def __init__(self):
      Employee.__empCount += 1
      print Employee.__empCount

emp1 = Employee()
emp2 = Employee()

print Employee.__empCount

Output will be-

1
2

Traceback (most recent call last):
  File "C:\Python27\Python resources\Programs\OOP\3.py", line 13, in <module>
    print Employee.__empCount
AttributeError: class Employee has no attribute '__empCount'

The only fundamental change we made in this program is that we put double underscore before class variable empCount. In doing so we accomplished something called data hiding. Unlike before, class variable is now inaccessible from outside. And for this reason, the statement print Employee.__empCount didn’t execute as it was trying to access variable from outside.


__del__ method

Modify the Program 6 again as shown below

Program 9

class Employee:

    empCount = 0

    def __init__(self):

        Employee.empCount += 1
        print Employee.empCount

    def __del__(self):

        Employee.empCount -= 1
        print Employee.empCount

emp1 = Employee()
emp2 = Employee()

Run above program and then in IDLE, issue following command –

>>>
1
2
>>> del emp1
1
>>> del emp2
0

__str__ method

Program 10

class Test:
    def __init__(self, name):
        self.name = name

    def __str__(self):

        return "name is %s" %self.name

test = Test('random')

Save this program as print.py and then run it. As such there won’t be any output because we really didn’t issue any command in original program. In IDLE, issue command print test. It will output –

>>> print test
name is random
>>>

Notice how print command is essentially invoking __str__ method. This is similar to Program 9 above where  del invoked the __del__ method.


__len__ method

Program 11

class Book:
    def __init__(self, page):
        self.page = page

    def __len__(self):

        return self.page

book = Book(55)

Just like Program 10, run this program and issue command len(book) in IDLE. It will output –

>>> len(book)
55
>>>

__add__ method

Program 12

class Strings:
    def __init__(self, a):
        self.a = a

    def __add__(self, other):
        return self.a + other.a

a = Strings('a')
b = Strings('c')

print a + b

Running this code will output ac.


Inheritance

Observe following program and its output –

class Super:
    def method(self):
        print 'in Super.method'

class Sub(Super):
    def method(self):
        print 'starting Sub.method'
        Super.method(self)
        print 'ending Sub.method'

x = Super()
y = Sub()

Output will be –

>>>
in Super.method
starting Sub.method
in Super.method
ending Sub.method

Though we have modified method in Sub class, we were still be able to call method from Super in Sub.

Advertisements