Classes
Motivation
We aim at avoiding code like this:
import math
x = 2
y = 3
xList = [1, 2, 3, 4]
yList = [1, 2, 3, 4]
def norm2(x, y):
return math.sqrt(self.x ** 2 + self.y ** 2)
- Are variables
x
andy
? - Are
xList
andyList
together mean list of points?
A first answer: struct
Making a struct
like in C by grouping data together.
import math
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
v = Vector2D(2, 3)
L = [Vector2D(1, 1), Vector2D(2, 2), Vector2D(3, 3), Vector2D(4, 4)]
def norm(v: Vector2D):
return math.sqrt(v.x ** 2 + v.y ** 2)
norm(v)
3.605551275463989
A class = struct + methods
A class mixes both data (fields and attributes) and methods (= functions applied to data).
Advantages:
- Functions are no more lost and the code is more organized (kind of namespace)
- More obvious to know on what the method is applied on
This is called encapsulation.
import math
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
@property
def norm(self):
return math.sqrt(self.x ** 2 + self.y ** 2)
def scale(self, scalar):
self.x *= scalar
self.y *= scalar
v = Vector2D(2, 1)
v.scale(0.5)
print(v.norm)
v.scale(4)
print(v.norm)
print(v.x)
print(v.y)
1.118033988749895
4.47213595499958
4.0
2.0
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[8], line 8
6 print(v.x)
7 print(v.y)
----> 8 v.norm = 3
AttributeError: can't set attribute
v
is (points to) an object. We say that v
is an instance of the class* Vector2D
. Of course, we can have several instances:
v = Vector2D(2, 1)
w = Vector2D(1, 1)
The concept of class (and of object-oriented programming) appears in many languages: C++, Java, Javascript, C#, etc.
Dataclasses
Problems
When we defined a class Vector2D
, equalities is naïve.
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
Vector2D(1, 2) == Vector2D(1, 2) # oh no!!
False
Also printing a Vector2D
is not very informative.
print(Vector2D(1, 2))
<__main__.Vector2D object at 0x7f69bc14aac0>
Cumbersome solution...
A cumbersome solution would be to write the magic method __eq__
ourselves.
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (self.x == other.x) and (self.y == other.y)
Vector2D(2, 3) == Vector2D(2, 3)
True
In the same way, we can define the magic method __repr__
for having pretty printing.
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (self.x == other.y) and (self.y == other.y)
def __repr__(self):
return f"(x={self.x}, y={self.y})"
print(Vector2D(1, 2))
(x=1, y=2)
Pythonic solution
Python provides dataclasses for that. See https://peps.python.org/pep-0557/. We do not have to write __eq__
, but actually we do not have to write __init__
, __repr__
, etc. and many other standard magic functions. Look:
from dataclasses import dataclass
@dataclass
class Vector2D:
"""Class for representing a point in 2D."""
x: float
y: float
v = Vector2D(1, 2)
w = Vector2D(1, 2)
print(v == w)
print(v)
True
Vector2D(x=1, y=2)
@dataclass
is a class decorator.
Methods
A method is a function applied on an object and defined in a class
. In object-oriented object, the goal is to have different objects interacting via message-passing. For instance speak
asks the cat to speak.
class Cat():
name = "Garfield"
def speak(self):
print(f"{self.name} says miaou.")
c = Cat()
c.speak()
c.name = "Felix"
c.speak()
Garfield says miaou.
Felix says miaou.
Of course, methods can have parameters than the object itself.
class Cat():
name = "Garfield"
def speak(self, strength):
print(f"{self.name} says {'miaou' if strength < 5 else 'MIAOU'}.")
c = Cat()
d = Cat()
c.speak(2)
d.speak(5)
Garfield
graou says miaou.
Garfield says MIAOU.
Abstraction
Abstraction consists in hidding the low-level details of an object from the user of the object.
- In some languages, e.g. C++ and Java, we have keywords like private VS public to explicitely say whether a field/method is private/public.
- In Python, we implicitely inform the user that an attribute/method is private by starting its name with
__
(and not finishing with__
otherwise you get a magic function 😉). See https://peps.python.org/pep-0008/
class Animal:
def __init__(self, health):
self.__health = health
def showHealth(self):
print(self.__health)
a = Animal(5)
a.showHealth()
a.__health
5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[2], line 10
8 a = Animal(5)
9 a.showHealth()
---> 10 a.__health
AttributeError: 'Animal' object has no attribute '__health'
Actually, it creates a field _Animal__health
. This is super ugly!
a._Animal__health
5
Exercice: Write a class for a grid graph created with an image.