Jupyter Snippet P4M 07

Jupyter Snippet P4M 07

All of these python notebooks are available at https://gitlab.erc.monash.edu.au/andrease/Python4Maths.git

Classes

Variables, Lists, Dictionaries etc in python are objects. Without getting into the theory part of Object Oriented Programming, explanation of the concepts will be done along this tutorial.

A class is declared as follows

class class_name:
    methods (functions)```



```python
class FirstClass:
    "This is an empty class"
    pass

pass in python means do nothing. The string defines the documentation of the class, accessible via help(FirstClass)

Above, a class object named “FirstClass” is declared now consider a “egclass” which has all the characteristics of “FirstClass”. So all you have to do is, equate the “egclass” to “FirstClass”. In python jargon this is called as creating an instance. “egclass” is the instance of “FirstClass”

egclass = FirstClass()
type(egclass)
__main__.FirstClass
type(FirstClass)
type

Objects (instances of a class) can hold data. A variable in an object is also called a field or an attribute. To access a field use the notation object.field. For example:x

obj1 = FirstClass()
obj2 = FirstClass()
obj1.x = 5
obj2.x = 6
x = 7
print("x in object 1 =",obj1.x,"x in object 2=",obj2.x,"global x =",x)
x in object 1 = 5 x in object 2= 6 global x = 7

Now let us add some “functionality” to the class. A function inside a class is called as a “Method” of that class

class Counter:
    def reset(self,init=0):
        self.count = init
    def getCount(self):
        self.count += 1
        return self.count
counter = Counter()
counter.reset(0)
print("one =",counter.getCount(),"two =",counter.getCount(),"three =",counter.getCount())
one = 1 two = 2 three = 3

Note that the reset() and function and the getCount() method are callled with one less argument than they are declared with. The self argument is set by Python to the calling object. Here counter.reset(0) is equivalent to Counter.reset(counter,0). Using self as the name of the first argument of a method is simply a common convention. Python allows any name to be used.

Note that here it would be better if we could initialise Counter objects immediately with a default value of count rather than having to call reset(). A constructor method is declared in Python with the special name __init__:

class FirstClass:
    def __init__(self,name,symbol):
        self.name = name
        self.symbol = symbol

Now that we have defined a function and added the __init__ method. We can create a instance of FirstClass which now accepts two arguments.

eg1 = FirstClass('one',1)
eg2 = FirstClass('two',2)
print(eg1.name, eg1.symbol)
print(eg2.name, eg2.symbol)
one 1
two 2

dir( ) function comes very handy in looking into what the class contains and what all method it offers

print("Contents of Counter class:",dir(Counter) )
print("Contents of counter object:", dir(counter))
Contents of Counter class: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getCount', 'reset']
Contents of counter object: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'count', 'getCount', 'reset']

dir( ) of an instance also shows it’s defined attributes so the object has the additional ‘count’ attribute. Note that Python defines several default methods for actions like comparison (__le__ is $\le$ operator). These and other special methods can be defined for classes to implement specific meanings for how object of that class should be compared, added, multiplied or the like.

Changing the FirstClass function a bit,

Just like global and local variables as we saw earlier, even classes have it’s own types of variables.

Class Attribute : attributes defined outside the method and is applicable to all the instances.

Instance Attribute : attributes defined inside a method and is applicable to only that method and is unique to each instance.

class FirstClass:
    test = 'test'
    def __init__(self,n,s):
        self.name = n
        self.symbol = s

Here test is a class attribute and name is a instance attribute.

eg3 = FirstClass('Three',3)
print(eg3.test,eg3.name,eg3.symbol)
TEST Three 3

Inheritance

There might be cases where a new class would have all the previous characteristics of an already defined class. So the new class can “inherit” the previous class and add it’s own methods to it. This is called as inheritance.

Consider class SoftwareEngineer which has a method salary.

class SoftwareEngineer:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def salary(self, value):
        self.money = value
        print(self.name,"earns",self.money)
a = SoftwareEngineer('Kartik',26)
a.salary(40000)
Kartik earns 40000
[ name for name in dir(SoftwareEngineer) if not name.startswith("_")]
['salary']

Now consider another class Artist which tells us about the amount of money an artist earns and his artform.

class Artist:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def money(self,value):
        self.money = value
        print(self.name,"earns",self.money)
    def artform(self, job):
        self.job = job
        print(self.name,"is a", self.job)
b = Artist('Nitin',20)
b.money(50000)
b.artform('Musician')
Nitin earns 50000
Nitin is a Musician
[ name for name in dir(b) if not name.startswith("_")]
['age', 'artform', 'job', 'money', 'name']

money method and salary method are the same. So we can generalize the method to salary and inherit the SoftwareEngineer class to Artist class. Now the artist class becomes,

class Artist(SoftwareEngineer):
    def artform(self, job):
        self.job = job
        print self.name,"is a", self.job
c = Artist('Nishanth',21)
dir(Artist)
['__doc__', '__init__', '__module__', 'artform', 'salary']
c.salary(60000)
c.artform('Dancer')
Nishanth earns 60000
Nishanth is a Dancer

Suppose say while inheriting a particular method is not suitable for the new class. One can override this method by defining again that method with the same name inside the new class.

class Artist(SoftwareEngineer):
    def artform(self, job):
        self.job = job
        print(self.name,"is a", self.job)
    def salary(self, value):
        self.money = value
        print(self.name,"earns",self.money)
        print("I am overriding the SoftwareEngineer class's salary method")
c = Artist('Nishanth',21)
c.salary(60000)
c.artform('Dancer')
Nishanth earns 60000
I am overriding the SoftwareEngineer class's salary method
Nishanth is a Dancer

If the number of input arguments varies from instance to instance asterisk can be used as shown.

class NotSure:
    def __init__(self, *args):
        self.data = ' '.join(list(args)) 
yz = NotSure('I', 'Do' , 'Not', 'Know', 'What', 'To','Type')
yz.data
'I Do Not Know What To Type'

Introspection

We have already seen the dir() function for working out what is in a class. Python has many facilities to make introspection easy (that is working out what is in a Python object or module). Some useful functions are hasattr, getattr, and setattr:

ns = NotSure('test')
if hasattr(ns,'data'): # check if ns.data exists
    setattr(ns,'copy', # set ns.copy
            getattr(ns,'data')) # get ns.data
print('ns.copy =',ns.copy)
ns.copy = test