classes : rappels (1)¶
les classes servent à définir de nouveau types
en sus des types prédéfinis
str
,list
,set
,dict
, …plus adaptés à l’application
class
¶
avec le mot-clé
class
on définit un nouveau typeune classe définit des méthodes spéciales
ici__init__
et__repr__
class User:
# le constructeur
def __init__(self, name, age):
# un objet User a deux attributs
# name et age
self.name = name
self.age = age
# l'afficheur
def __repr__(self):
return f"{self.name}, {self.age} ans"
# une fois qu'on a défini une classe,
# on peut s'en servir pour créer
# des objets - on dit des instances
# de la classe
user1 = User("Lambert", 25)
user1
Lambert, 25 ans
une classe est un type¶
comme tous les types, la classe est une usine à objets
user = User("Dupont", 59)
à rapprocher de
s = set()
oun = int('32')
chaque objet (on dit instance) contient des données
rangées dans des attributs de l’objet
ici
name
etage
affichage¶
la méthode spéciale
__repr__(self)
doit renvoyer une chaineelle est utilisée pour
imprimer l’objet avec
print()
convertir un objet en chaine
print(f"je viens de voir {user1}")
je viens de voir Lambert, 25 ans
str(user1)
'Lambert, 25 ans'
affichage et conversion en chaine¶
en fait il est possible d’être plus fin, et de définir deux méthodes spéciales qui sont
__repr__(self)
et__str__(self)
cela dit pour commencer on peut se contenter de ne définir que __repr__()
qui est alors utilisée pour tous les usages
méthodes¶
une classe peut définir des méthodes
qui sont des fonctions qui s’appliquent sur un objet (de cette classe)
# une implémentation très simple
# d'une file FILO
# premier entré dernier sorti
class Stack:
def __init__(self):
self.frames = []
def __repr__(self):
return " > ".join(self.frames)
def push(self, item):
self.frames.append(item)
def pop(self):
return self.frames.pop()
# instance
stack = Stack()
stack.push('fact(3)')
stack.push('fact(2)')
stack.push('fact(1)')
stack
fact(3) > fact(2) > fact(1)
stack.pop()
'fact(1)'
stack
fact(3) > fact(2)
méthodes et paramètres¶
remarquez qu’ici
on a défini la méthode
push
avec 2 paramètres
def push(self, item):
ce qui fait qu’on peut l’appeler sur un objet avec 1 paramètre
stack.push(some_item)
car le premier paramètre
self
est lié
à l’objet sur lequel on envoie la méthodeet la phrase
stack.push(some_item)
est en fait équivalente àStack.push(stack, some_item)
intérêts de cette approche¶
définir vos propres types de données
grouper les données qui vont ensemble dans un
objet unique, facile à passer à d’autres fonctionsinvariants: garantir de bonnes propriétés si on utilise les objets au travers des méthodes
et aussi (sera vu ultérieurement)
héritage
réutiliser une classe en modifiant
seulement quelques aspects
intégrer les objets dans le langage
i.e. donner un sens à des constructions commex in obj
obj[x]
if obj:
for item in obj:
…
exemples¶
exemple : np.ndarray
¶
c’est une classe que vous utilisez tous les jours !
en fait il n’y a pas de différence de fond
entre les types prédéfinis (
str
, …)et les classes créées avec
class
exemple : class Point
¶
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"({self.x:.2f} x {self.y:.2f})"
def distance(self, other):
return math.sqrt((self.x-other.x)**2 + (self.y-other.y)**2)
a = Point(4, 3)
b = Point(7, 7)
a, b
((4.00 x 3.00), (7.00 x 7.00))
a.distance(b)
5.0
exemple : class Circle
(1)¶
class Circle1:
def __init__(self, center: Point, radius: float):
self.center = center
self.radius = radius
def __repr__(self):
return f"[{self.center} ⟷ {self.radius:.2f}]"
def contains(self, point: Point):
"""
returns a bool; does point belong in the circle ?
"""
print(self.center.distance(point))
return math.isclose(self.center.distance(point), self.radius)
c1 = Circle1(Point(0, 0), 5)
c1.contains(a)
5.0
True
exemple : class Circle
(2)¶
class Circle2:
def __init__(self, center: Point, radius: float):
self.center = center
self.radius = radius
def __repr__(self):
return f"[{self.center} ⟷ {self.radius:.2f}]"
# si on transforme cette méthode en méthode spéciale...
def __contains__(self, point: Point):
"""
returns a bool; does point belong in the circle ?
"""
print(self.center.distance(point))
return math.isclose(self.center.distance(point), self.radius)
c2 = Circle2(Point(0, 0), 5)
# alors on peut faire le même calcul, mais
# l'écrire comme un test d'appartenance habituel 'x in y'
a in c2
5.0
True
exemple : class datetime.date
etc..¶
bien sûr il y a des classes dans la bibliothèque standard
voyez par exemple le module
datetime
et notamment
datetime.date
(une date)
etdatetime.timedelta
(une durée)
bien sûr il y a des classes dans la bibliothèque standard
voyez par exemple le module
datetime
et notamment
datetime.date
(une date)
etdatetime.timedelta
(une durée)
# normalement la classe date aurait dû s'appeler Date
from datetime import date as Date
# pareil
from datetime import timedelta as TimeDelta
Date.today()
datetime.date(2021, 8, 16)
TimeDelta(weeks=2)
datetime.timedelta(days=14)
# il y a 3 semaines nous étions le
today = Date.today()
three_weeks = 3 * TimeDelta(weeks=1)
today - three_weeks
datetime.date(2021, 7, 26)
def timedelta_as_year_month(age):
"""
convert a duration in years and months (as a str)
"""
year = TimeDelta(days=365.2425)
years, leftover = age // year, age % year
month = year/12
months, leftover = leftover // month, leftover % month
return f"{years} ans, {months} mois"
exemple : class Student
¶
class Student:
def __init__(self, first_name, last_name,
birth_year, birth_month, birth_day):
self.first_name = first_name
self.last_name = last_name
self.birth_year = birth_year
self.birth_month = birth_month
self.birth_day = birth_day
def __repr__(self):
return f"{self.first_name} {self.last_name}"
def age(self):
"""
retourne un TimeDelta
"""
birth = Date(self.birth_year, self.birth_month, self.birth_day)
now = Date.today()
# la différence entre 2 Dates c'est une
return now - birth
def repr_age(self):
return timedelta_as_year_month(self.age())
class Student
- utilisation¶
achille = Student("Achille", "Talon", 2001, 7, 14)
achille
Achille Talon
achille.age()
datetime.timedelta(days=7338)
type(achille.age())
datetime.timedelta
print(f"{achille} a {achille.repr_age()}")
Achille Talon a 20 ans, 1 mois
exemple : class Class
¶
(dans le sens: groupe de Student
s)
bien sûr on peut combiner nos types (les classes)
avec les types de baseet ainsi créer e.g. des listes de
Student
class Class:
def __init__(self, classname, students):
self.classname = classname
self.students = students
def __repr__(self):
return f"{self.classname} with {len(self.students)} students"
def average_age(self):
# on aimerait pouvoir écrire simplement ceci
# return ((sum(student.age() for student in self.students)
# / len(self.students))
# mais ça ne fonctionne pas, il faut passer à sum
# l'élément neutre de l'addition - ici TimeDelta(0)
# car '0' ne peut pas s'additionner à un TimeDelta
return (sum((student.age() for student in self.students), TimeDelta(0))
/ len(self.students))
class Class
- utilisation¶
hilarion = Student("Hilarion", "Lefuneste", 1998, 10, 15)
gaston = Student("Gaston", "Lagaffe", 1995, 2, 28)
haddock = Student("Capitaine", "Haddock", 2000, 1, 14)
tournesol = Student("Professeur", "Tournesol", 1996, 2, 29)
# attention je ne peux pas utiliser une variable
# qui s'appellerait 'class' car c'est un mot-clé de Python
cls = Class("CIC1A", [achille, hilarion, gaston, haddock, tournesol])
cls
CIC1A with 5 students
# la moyenne d'âge de la classe
cls.average_age()
datetime.timedelta(days=8506)
# la moyenne d'âge de la classe, pour les humains
timedelta_as_year_month(cls.average_age())
'23 ans, 3 mois'
résumé (1/2)¶
avec
class
on peut définir un nouveau typequi nous permet de créer des objets
qui représentent mieux que les types de base les données de notre application
pas de différence entre un type prédéfini et une classe :
un objet créé par une classe s’utilise normalementune variable peut désigner un objet
un objet peut être dans une liste (ou autre type) builtin
(attention pour les clés dedict
qui doivent être immutables)ou passé en paramètre à une fonction,
etc, etc…
résumé (2/2)¶
une classe peut définir des méthodes
qui travaillent sur un objet (souvent appelé
self
)souvent on ne modifie les objets
qu’au travers des méthodes fournies par la classece qui permet de garantir certains invariants