|
|
@@ -0,0 +1,432 @@ |
|
|
|
% Programmation avec Python (chapitre 8) |
|
|
|
% Dimitri Merejkowsky |
|
|
|
|
|
|
|
\center \huge Rappels |
|
|
|
|
|
|
|
# Définition et utilisation d'une classe |
|
|
|
|
|
|
|
```python |
|
|
|
class Counter: |
|
|
|
def __init__(self): |
|
|
|
self.count = 0 |
|
|
|
|
|
|
|
def increment(self, times=1): |
|
|
|
self.count += times |
|
|
|
|
|
|
|
>>> counter = Counter() |
|
|
|
>>> counter.increment(times=2) |
|
|
|
>>> counter.count |
|
|
|
2 |
|
|
|
>>> counter.increment() |
|
|
|
>>> counter.count |
|
|
|
3 |
|
|
|
``` |
|
|
|
|
|
|
|
# Vocabulaire |
|
|
|
|
|
|
|
* `counter` est une *instance* de la *classe* `Counter` |
|
|
|
* `increment` est une *méthode d'instance* |
|
|
|
* `count` est un *attribut d'instance* |
|
|
|
* `__init__` est un *constructeur* |
|
|
|
|
|
|
|
# Méthodes et attributs de classe |
|
|
|
|
|
|
|
```python |
|
|
|
class Car: |
|
|
|
total_number_of_cars = 0 |
|
|
|
|
|
|
|
def __init__(self, color="black"): |
|
|
|
self.color = color |
|
|
|
Car.total_number_of_cars += 1 |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def print_number_of_cars(cls): |
|
|
|
print(cls.total_number_of_cars, |
|
|
|
"cars have been made") |
|
|
|
|
|
|
|
>>> ford = Car() |
|
|
|
>>> ferrari = Car(color="red") |
|
|
|
>>> Car.print_number_of_cars() |
|
|
|
2 cars have been made |
|
|
|
``` |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
\center \huge Composition |
|
|
|
|
|
|
|
# Composition |
|
|
|
|
|
|
|
* Quand on met une classe dans une autre. |
|
|
|
|
|
|
|
* Par exemple, le constructeur de la classe A va prendre en paramètre une |
|
|
|
instance de la classe B. |
|
|
|
|
|
|
|
* Introduit un *couplage* entre les classes A et B. |
|
|
|
|
|
|
|
# Example |
|
|
|
|
|
|
|
```python |
|
|
|
class Authorization: |
|
|
|
def __init__(self, credentials_file): |
|
|
|
... |
|
|
|
self.password = ... |
|
|
|
|
|
|
|
class Client: |
|
|
|
url = "https://exmple.com" |
|
|
|
def __init__(self, auth) |
|
|
|
self.auth = auth |
|
|
|
|
|
|
|
def make_request(self): |
|
|
|
password = self.auth.password |
|
|
|
requests.get(url, password=password) |
|
|
|
``` |
|
|
|
|
|
|
|
# Couplage 1 |
|
|
|
|
|
|
|
Il faut construire une instance d'`Authorization` pour pouvoir |
|
|
|
construire une instance de `Client` |
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
>>> auth = Authorization("credentials.txt") |
|
|
|
>>> client = Client(auth) |
|
|
|
>>> client.make_request() |
|
|
|
``` |
|
|
|
|
|
|
|
# Couplage 2 |
|
|
|
|
|
|
|
Si jamais l'atttribut `password` dans la classe `Authorization` change, |
|
|
|
le code dans `Client.make_request()` devra changer aussi. |
|
|
|
|
|
|
|
```python |
|
|
|
class Authorization: |
|
|
|
... |
|
|
|
self.password = ... |
|
|
|
|
|
|
|
|
|
|
|
class Client: |
|
|
|
... |
|
|
|
|
|
|
|
def make_request(self): |
|
|
|
password = self.auth.password |
|
|
|
... |
|
|
|
``` |
|
|
|
|
|
|
|
# Conclusion |
|
|
|
|
|
|
|
Prenez le temps d'étudier les relations entre les différentes classes! |
|
|
|
|
|
|
|
Souvent un simple schéma suffira. |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
\center \huge Héritage |
|
|
|
|
|
|
|
# Petit détour |
|
|
|
|
|
|
|
Qu'est-ce qui ne va pas dans ce code? |
|
|
|
|
|
|
|
```python |
|
|
|
def faire_le_cafe(): |
|
|
|
mettre_cafe_dans_tasse() |
|
|
|
allumer_bouilloire() |
|
|
|
attendre_que_ca_bouille() |
|
|
|
verser_dans_tasse() |
|
|
|
melanger() |
|
|
|
|
|
|
|
def faire_le_the(): |
|
|
|
mettre_the_dans_tasse() |
|
|
|
allumer_bouilloire() |
|
|
|
attendre_que_ca_bouille() |
|
|
|
verser_dans_tasse() |
|
|
|
laisser_infuser() |
|
|
|
``` |
|
|
|
|
|
|
|
# Duplication |
|
|
|
|
|
|
|
* Les lignes de `allumer_bouilloire()` à `verser_dans_tasse()` sont les mêmes |
|
|
|
* Le code est plus long |
|
|
|
* Si jamais la procédure pour faire chauffer l'eau change, il faudra changer |
|
|
|
le code a deux endroits différents |
|
|
|
|
|
|
|
# Solution: extraire une fonction |
|
|
|
|
|
|
|
```python |
|
|
|
def faire_chauffer_l_eau(): |
|
|
|
allumer_bouilloire() |
|
|
|
attendre_que_ca_bouille() |
|
|
|
|
|
|
|
def faire_le_cafe(): |
|
|
|
mettre_cafe_dans_tasse() |
|
|
|
faire_chauffer_l_eau() |
|
|
|
verser_dans_tasse() |
|
|
|
melanger() |
|
|
|
|
|
|
|
def faire_le_the(): |
|
|
|
mettre_the_dans_tasse() |
|
|
|
faire_chauffer_l_eau() |
|
|
|
verser_dans_tasse() |
|
|
|
laisser_infuser() |
|
|
|
``` |
|
|
|
|
|
|
|
# Facile à changer |
|
|
|
|
|
|
|
Si maintenant il faut débrancher le grille-pain avant de pouvoir faire chauffer l'eau, |
|
|
|
on a juste à changer une fonction: |
|
|
|
|
|
|
|
```python |
|
|
|
def faire_chauffer_l_eau(): |
|
|
|
debrancher_grille_pain() |
|
|
|
brancher_bouilloire() |
|
|
|
allumer_bouilloire() |
|
|
|
attendre_que_ca_bouille() |
|
|
|
``` |
|
|
|
|
|
|
|
# Note |
|
|
|
|
|
|
|
Notez qu'on a *laissé* la ligne `verser_dans_tasse()` dupliquée. |
|
|
|
|
|
|
|
C'est une duplication par *coïncidence*. |
|
|
|
|
|
|
|
Et ça nous permet de faire ça: |
|
|
|
|
|
|
|
```python |
|
|
|
def faire_le_the(): |
|
|
|
faire_chauffer_l_eau() |
|
|
|
mettre_the_dans_tasse() |
|
|
|
verser_dans_tasse() |
|
|
|
laisser_infuser() |
|
|
|
``` |
|
|
|
|
|
|
|
# Conclusion |
|
|
|
|
|
|
|
Encore une fois, réfléchissez avant d'agir! |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
\center \huge Héritage |
|
|
|
|
|
|
|
# Un autre type de relation entre classes |
|
|
|
|
|
|
|
Si la composition est une relation "has-a", l'héritage décrit |
|
|
|
une relation "is-a". |
|
|
|
|
|
|
|
# Composition |
|
|
|
|
|
|
|
```python |
|
|
|
class Dog: |
|
|
|
pass |
|
|
|
|
|
|
|
class Person: |
|
|
|
def __init__(self, pet=None): |
|
|
|
self.pet = pet |
|
|
|
|
|
|
|
>>> nestor = Dog() |
|
|
|
>>> john = Person(pet=nestor) |
|
|
|
>>> jonh.pet |
|
|
|
nestor |
|
|
|
``` |
|
|
|
|
|
|
|
`John` *a* un animal. |
|
|
|
|
|
|
|
|
|
|
|
# Héritage |
|
|
|
|
|
|
|
```python |
|
|
|
class Animal: |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class Dog(Animal): |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class Cat(Animal): |
|
|
|
pass |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
`Dog` et `Cat` *sont* des animaux. |
|
|
|
|
|
|
|
# Vocabulaire |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
... |
|
|
|
|
|
|
|
class B(A): |
|
|
|
... |
|
|
|
``` |
|
|
|
|
|
|
|
* A est la classe *parente* de B. |
|
|
|
* B *hérite* de A. |
|
|
|
* B est une classe *file* de A. |
|
|
|
|
|
|
|
|
|
|
|
# Utilisation |
|
|
|
|
|
|
|
* Si une méthode n'est pas trouvée dans la classe courante, |
|
|
|
Python ira la chercher dans la classe parente |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def method_in_a(self): |
|
|
|
print("in a") |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def method_in_b(self): |
|
|
|
print("in b") |
|
|
|
|
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.method_in_b() |
|
|
|
'in b' # comme d'habitude |
|
|
|
>>> b.method_in_a() |
|
|
|
'in a' |
|
|
|
``` |
|
|
|
|
|
|
|
# Ordre de résolution |
|
|
|
|
|
|
|
S'il y a plusieurs classes parentes, Python les remonte toutes: |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def method_in_a(self): |
|
|
|
print("in a") |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def method_in_b(self): |
|
|
|
... |
|
|
|
|
|
|
|
class C(B): |
|
|
|
def method_in_c(self): |
|
|
|
... |
|
|
|
|
|
|
|
>>> c = C() |
|
|
|
>>> c.method_in_a() |
|
|
|
'in a' |
|
|
|
``` |
|
|
|
|
|
|
|
# Avec `__init__` |
|
|
|
|
|
|
|
La résolution fonctionne pour toutes les méthodes, y compris `__init__` |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def __init__(self): |
|
|
|
print("Building parent") |
|
|
|
|
|
|
|
class B(A): |
|
|
|
... |
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
Building parent |
|
|
|
``` |
|
|
|
|
|
|
|
# Attributs |
|
|
|
|
|
|
|
Même méchanisme pour les attributs: |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def __init__(self): |
|
|
|
self.a_attribute = 42 |
|
|
|
|
|
|
|
class B(A): |
|
|
|
... |
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.a_attribute |
|
|
|
42 |
|
|
|
``` |
|
|
|
|
|
|
|
# Overriding |
|
|
|
|
|
|
|
On peut aussi *écraser* la méthode du parent dans l'enfant: |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def my_method(self): |
|
|
|
print("method in A") |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def my_method(self): |
|
|
|
print("method in B") |
|
|
|
|
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.my_method() |
|
|
|
"method in B" |
|
|
|
``` |
|
|
|
|
|
|
|
# super() |
|
|
|
|
|
|
|
Demande à chercher une méthode dans la classe parente |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def a_method(self): |
|
|
|
print("method in A") |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def b_method(self): |
|
|
|
super().a_method() |
|
|
|
print("method in B") |
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.b_method() |
|
|
|
method in A |
|
|
|
method in B |
|
|
|
``` |
|
|
|
|
|
|
|
# `super` et `__init__` |
|
|
|
|
|
|
|
Erreur très courante: |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def __init__(self): |
|
|
|
self.a_attribute = "foo" |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def __init__(self): |
|
|
|
self.b_attribute = 42 |
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.b_attribute |
|
|
|
42 |
|
|
|
>>> b.a_attribute |
|
|
|
AttributeError |
|
|
|
``` |
|
|
|
|
|
|
|
On a écrasé `A.__init__`! |
|
|
|
|
|
|
|
# `super` et `__init__` |
|
|
|
|
|
|
|
```python |
|
|
|
class A: |
|
|
|
def __init__(self): |
|
|
|
self.a_attribute = "foo" |
|
|
|
|
|
|
|
class B(A): |
|
|
|
def __init__(self): |
|
|
|
super().__init__() |
|
|
|
self.b_attribute = 42 |
|
|
|
|
|
|
|
>>> b = B() |
|
|
|
>>> b.b_attribute |
|
|
|
42 |
|
|
|
>>> b.a_attribute |
|
|
|
"foo" # OK |
|
|
|
``` |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
|
|
|
|
\center \huge Atelier |
|
|
|
|
|
|
|
|
|
|
|
# Objectif |
|
|
|
|
|
|
|
*Une autre vision de l'héritage*: on va rajouter une fonctionnalité dans |
|
|
|
notre script marvel, puis on va réduire le code dupliqué. |