Classes
Contents
Classes#
Classes provide a means of bundling data and functionality together.
Creating a new class creates a new type of object.
Assigned variables are new instances of that type.
Each class instance can have attributes attached to it.
Class instances can also have methods for modifying its state.
Python classes provide the class inheritance mechanism.
Use class to store data#
A empty class can be used to bundle together a few named data items.
You can easily save this class containing your data in JSON file.
class Car:
pass
mycar = Car() # Create an empty car record
# Fill the fields of the record
mycar.brand = 'Peugeot'
mycar.model = 308
mycar.year = 2015
mycar.__dict__
{'brand': 'Peugeot', 'model': 308, 'year': 2015}
namedtuple#
from collections import namedtuple
Car = namedtuple('Car', 'brand, model, year')
mycar = Car('Peugeot', 308, 2015)
mycar
Car(brand='Peugeot', model=308, year=2015)
mycar.year
2015
# Like tuples, namedtuples are immutable:
import sys
try:
mycar.model = 3008
except:
print(sys.exc_info()[0])
<class 'AttributeError'>
class Car:
"A simple example class Animal with its name, weight and age"
def __init__(self, brand, model, year): # constructor
self.brand = brand
self.model = model
self.year = year
def age(self): # method
import datetime
now = datetime.datetime.now()
return now.year - self.year
mycar = Car('Peugeot', 308, 2015) # Instance
print(f' {mycar.brand} {mycar.model} {mycar.year}')
print(f' {mycar.age()} years old')
Peugeot 308 2015
8 years old
mycar.year = 2017
mycar.age()
6
mycar
is an instance of Car Class.mycar.age()
is a method ofCar
instancemycar
.brand
andmodel
are attributes ofCar
instancemycar
.
Convert method to attribute#
Use the property
decorator
class Car:
"A simple example class Car with its model, brand and year"
def __init__(self, brand, model, year): # constructor
self.model = model
self.brand = brand
self.year = year
@property
def age(self): # method
import datetime
now = datetime.datetime.now()
return now.year - self.year
mycar = Car('Peugeot', 308, 2015)
mycar.age # age can now be used as an attribute
8
try:
mycar.age = 3 # a protected attribute
except:
print(sys.exc_info()[0])
<class 'AttributeError'>
The new Python 3.7 DataClass#
from dataclasses import dataclass
@dataclass
class Car:
brand: str
model: int
year: int
@property
def age(self) -> int:
import datetime
now = datetime.datetime.now()
return now.year - self.year
mycar = Car('Peugeot', 308, 2015)
mycar
Car(brand='Peugeot', model=308, year=2015)
myothercar = Car('BMW', "1 Series", 2009)
myothercar
Car(brand='BMW', model='1 Series', year=2009)
Method Overriding#
Every Python classes has a
__repr__()
method used when you callprint()
function.
class Car:
"""Simple example class with method overriding """
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.brand} {self.model} {self.__class__.__name__}"
mycar = Car('Peugeot', 308, 2015)
print(mycar)
2015 Peugeot 308 Car
Inheritance#
class Rectangle(): # Parent class is defined here
def __init__(self, width, height):
self.width, self.height = width, height
@property
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, edge):
super().__init__(edge, edge) # Call method in the parent class
r = Rectangle(2, 3)
print(f"Rectangle area \t = {r.area:7.3f}")
s = Square(4)
print(f"Square area \t = {s.area:7.3f}")
Rectangle area = 6.000
Square area = 16.000
Private Variables and Methods#
class DemoClass:
" Demo class for name mangling "
def public_method(self):
return 'public!'
def __private_method(self): # Note the use of leading underscores
return 'private!'
object3 = DemoClass()
object3.public_method()
'public!'
try:
object3.__private_method()
except:
print(sys.exc_info()[0])
<class 'AttributeError'>
[ s for s in dir(object3) if "method" in s]
['_DemoClass__private_method', 'public_method']
object3._DemoClass__private_method()
'private!'
object3.public_method
<bound method DemoClass.public_method of <__main__.DemoClass object at 0x7f5a74b847c0>>
Use class
as a Function.#
class Polynomial:
" Class representing a polynom P(x) -> c_0+c_1*x+c_2*x^2+..."
def __init__(self, coeffs):
self.coeffs = coeffs
def __call__(self, x):
return sum(coef*x**exp for exp,coef in enumerate(self.coeffs))
p = Polynomial([2,4,-1])
p(2)
6
Exercise: Polynomial#
Improve the class above called Polynomial by creating a method
diff(n)
to compute the nth derivative.Override the
__repr__()
method to output a pretty printing.
Hint: f"{coeff:+d}"
forces to print sign before the value of an integer.
Operators Overriding#
class MyComplex:
" Simple class representing a complex"
width = 7
precision = 3
def __init__(self, real=0, imag=0):
self.real = real
self.imag = imag
def __repr__(self):
return (f"({self.real:{self.width}.{self.precision}f},"
f"{self.imag:+{self.width}.{self.precision}f}j)")
def __eq__(self, other): # override '=='
return (self.real == other.real) and (self.imag == other.imag)
def __add__(self, other): # override '+'
return MyComplex(self.real+other.real, self.imag+other.imag)
def __sub__(self, other): # override '-'
return MyComplex(self.real-other.real, self.imag-other.imag)
def __mul__(self, other): # override '*'
if isinstance(other, MyComplex):
return MyComplex(self.real * other.real - self.imag * other.imag,
self.real * other.imag + self.imag * other.real)
else:
return MyComplex(other*self.real, other*self.imag)
u = MyComplex(0, 1)
v = MyComplex(1, 0)
print('u=', u, "; v=", v)
u= ( 0.000, +1.000j) ; v= ( 1.000, +0.000j)
u+v, u-v, u*v, u==v
(( 1.000, +1.000j), ( -1.000, +1.000j), ( 0.000, +1.000j), False)
We can change the class attribute precision.
MyComplex.precision=2
print(u.precision)
print(u)
2
( 0.00, +1.00j)
v.precision
2
We can change the instance attribute precision.
u.precision=1
print(u)
( 0.0, +1.0j)
print(v)
( 1.00, +0.00j)
MyComplex.precision=5
u # set attribute keeps its value
( 0.0, +1.0j)
v # unset attribute is set to the new value
(1.00000,+0.00000j)
Rational example#
class Rational:
" Class representing a rational number"
def __init__(self, n, d):
assert isinstance(n, int) and isinstance(d, int)
def gcd(x, y):
if x == 0:
return y
elif x < 0:
return gcd(-x, y)
elif y < 0:
return -gcd(x, -y)
else:
return gcd(y % x, x)
g = gcd(n, d)
self.numer, self.denom = n//g, d//g
def __add__(self, other):
return Rational(self.numer * other.denom + other.numer * self.denom,
self.denom * other.denom)
def __sub__(self, other):
return Rational(self.numer * other.denom - other.numer * self.denom,
self.denom * other.denom)
def __mul__(self, other):
return Rational(self.numer * other.numer, self.denom * other.denom)
def __truediv__(self, other):
return Rational(self.numer * other.denom, self.denom * other.numer)
def __repr__(self):
return f"{self.numer:d}/{self.denom:d}"
r1 = Rational(2,3)
r2 = Rational(3,4)
r1+r2, r1-r2, r1*r2, r1/r2
(17/12, -1/12, 1/2, 8/9)
Exercise#
Improve the class Polynomial by implementing operations:
Overrides ‘+’ operator (add)
Overrides ‘-’ operator (neg)
Overrides ‘==’ operator (eq)
Overrides ‘*’ operator (mul)