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