Notes et transcriptions du cours “Apprenez la programmation orientée objet avec Python” disponible sur la plateforme Openclassrooms.
Lorsque nous programmons, nous stockons fréquemment les données dans une collection. Les collections comprennent des listes – où les données ont une position et sont indexables – et des dictionnaires – où on attribue une clé aux données.
Vous vous en souvenez peut-être, on peut placer tous types de données – y compris un mélange de types – dans une liste ou un dictionnaire :
numberList = [1.2, 3.5, 4.3, 2.8] phoneNumberDictionary = { "alice" : "01234 567890", "bob" : "01324 765098"} mixedList = [5, 4, 3, 2, 1, "boom"]
Attendez une seconde, je ne peux pas modifier un entier (int) ? Si je dis que x = 5, je peux paramétrer x par la suite pour qu’il soit égal à 6, non ?
Il faut faire la distinction entre “l’assignation” (le fait d’assigner une nouvelle valeur à une variable avec le signe égal), et la “modification” (le fait de modifier l’état d’un objet). Quand vous réassignez une variable, vous ne modifiez pas sa valeur actuelle, mais vous la changez pour une nouvelle valeur. ;)
Du coup, si vous fixez la valeur de x à 6, vous ne modifiez pas la valeur de l’entier 5 – elle existe toujours – vous modifiez uniquement ce que contient la variable x. Si je modifie l’attribut nom
d’un objet Personne
, je modifie l’état de cet objet.
Bref – les objets de n’importe quelle classe sont comme n’importe quel autre élément ! Ils peuvent eux aussi être stockés dans des collections, un peu comme ici ; disons qu’il s’agit de notre liste de bénévoles pour une collecte de nourriture locale :
class Person: """Personne.""" def __init__(self, name): """Donne un nom.""" self.name = name def walk(self): """Marche.""" print(f"{self.name} marche.") volunteers = [Person("Alice"), Person("Bob"), Person("Carol")] for volunteer in volunteers: volunteer.walk() # Ici, nous reprenons nos outils ! toolbox = { "screwdriver": Screwdriver(50), "hammer": Hammer(), "nails": [Nail(), Nail(), Nail()], }
De la même façon que précédemment, nous avons une liste et un dictionnaire – mais cette fois, la liste contient des Personnes
, et le dictionnaire contient toutes sortes de choses ! Nous avons même ici une boucle for, qui accomplit des itérations sur nos bénévoles (qui sont tous des objets Personne
) et qui invoque leur méthode walk()
.
Qu’est-ce qui pourrait bien mal se passer ?
Python utilise ce que l’on appelle le duck typing (littéralement, typage de canard) : si ça a un bec et que ça cancane comme un canard, alors c’est probablement un canard. Autrement dit, les méthodes ou attributs d’un objet sont plus importants que son type ou sa classe.
Prenons un exemple. Et si nos bénévoles étaient un peu différents cette semaine ?
volunteers = [Person("Alice"), Fish("Wanda"), Person("Bob")] for volunteer in volunteers: volunteer.walk() # Oops!
Les poissons ne peuvent pas marcher, donc si nous exécutons ce code, nous obtiendrons ce genre de chose : AttributeError: ‘Fish’ object has no attribute ‘walk’.
🐠🐠
Dans d’autres langages, vous auriez à définir la Liste comme contenant un type particulier – mais cela ne nous intéresse pas en Python ! Nous avons simplement une liste, et elle contient des choses : nous ne saurons pas que les poissons ne peuvent pas marcher, jusqu’à ce que notre programme plante. Oups. 😬
Alors, comment nous assurer que cela ne se produise pas ?
Il existe quelques stratégies que vous pouvez utiliser de différentes façons et à des degrés différents. Le choix des stratégies que vous utilisez dépendra fortement de la nature de votre programme.
Vous pouvez documenter votre code à l’aide de docstrings (pour les fonctions, classes, méthodes et modules). Je vous partage la documentation Python des conventions docstrings (en anglais), si vous êtes curieux. Vous avez dû remarquer que je les utilise souvent. Elles sont vos meilleures alliées pour comprendre le code.
L’utilisation de commentaires (“#”) est possible, mais évitez de les utiliser à toutes les sauces. Les docstrings devraient en effet suffire, elles sont une forme de documentation réelle pour votre code (elles peuvent se récupérer par votre éditeur de code, ou générer des documentations sous forme de site, via des bibliothèques spécialisées pour comme Sphinx).
Python 3 vous fournit une bibliothèque de types
, qui vous permet d’écrire du code avec des annotations de type, de façon similaire à d’autres langages. Cette possibilité est de plus en plus utilisée – suivez le guide de styles du projet (nous y reviendrons dans la partie suivante) pour savoir si vous l’utiliserez ou non.
Voici un exemple :
from typing import List def highest(numbers: List[int]) -> int: max_value = 0 for number in numbers: if number > max_value: max_value = number return max_value
Cette fonction retourne l’entier (int) le plus élevé d’une liste d’entiers. La signature de cette fonction dit que les nombres doivent constituer une List[int] – une liste d’entiers – et le type de retour de cette fonction est int.
Le typage peut être extrêmement utile pour vous assurer que les bonnes variables soient au bon endroit, mais il convient de souligner que Python ne garantit pas l’exactitude – par exemple, il vous serait toujours possible de mettre une liste de chaînes dans la fonction highest
(le plus élevé).
Le typage Python se situe totalement dans le champ de compétences du développeur, et plus probablement, de l’IDE.
Les IDE prenant en compte des bibliothèques de vérification de types (telles que mypy) pourront vous avertir d’un typage non respecté. L’avantage du typage réside aussi dans l’autocomplétion fournie par votre IDE. En effet, typer les paramètres de fonction, par exemple, permettra à votre IDE de proposer tous les attributs disponibles pour ce paramètre.
La base de l’approche de la programmation défensive, c’est de se prémunir contre la possibilité d’erreurs ou de fautes. Nous pouvons utiliser différents outils pour cela, et le plus simple d’entre eux est l’humble instructionif :
def divide(a, b): if b != 0: return a / b else: return 0
Ici, nous nous prémunissons contre la possibilité de diviser par zéro. Python fournit quelques autres outils, comme la déclaration assert
(affirmer) – souvent utilisée dans les tests – qui cause le déclenchement d’une Exception par un programme si quelque chose n’est pas vrai. Par exemple :
def divide(a, b): assert b != 0 return a / b
Si nous donnons à cette fonction diviser (divide
) un dénominateur de 0, le programme s’arrêtera avant d’essayer de résoudre la division. Attention cependant, le mot clé assert
n’est pas conseillé pour un code de production, comme nous pouvons voir sur cette page de documentation when to use assert(en anglais).
Python fournit également certaines fonctions pour garantir que les objets appartiennent à un type (isinstance
) ou ont un attribut spécifique (hasattr
), ce qui peut être utilisé ainsi :
for volunteer in volunteers: if hasattr(volunteer, "walk"): volunteer.walk() if not isinstance(x, Y): raise Z("M")
Attention, il est possible d’être excessivement défensif – après tout, chaque assertion, instruction if ou appel de fonction demande du temps informatique qui n’est pas dévolu à exécuter votre programme lui-même.
Certaines opérations non valides durant l’exécution d’un programme Python provoquent des exceptions – qui indiquent à l’utilisateur et à d’autres parties du programme qu’une erreur a eu lieu.
Nous pouvons écrire nos propres exceptions, faire en sorte qu’elles soient lancées en cas de problème, et gérer ces exceptions de différentes manières. Nous parlerons davantage des exceptions et de la gestion d’exception dans la troisième partie.
Les scripts et les programmes plus petits ou moins importants ont fréquemment une approche beaucoup plus passive de ces erreurs. Autrement dit, se contenter de laisser le programme planter peut parfois être une option ! Bien qu’il faille l’éviter pour des applications plus grosses ou plus importantes, c’est souvent la bonne solution si une situation est irrécupérable.
Pour finir, nous allons implémenter notre méthode d’affichage du fil de discussion dans le code de cette partie. Vous utiliserez le code de définition des classes d’utilisateurs, de fichiers, de posts et de thread implémentés jusqu' ici. Vous pouvez aussi vous baser sur le dernier corrigé sur Github.
Suivez les étapes d’implémentation :
N’oubliez pas d’afficher le fil de discussion sur le temps pour vérifier son état (les messages qui lui sont assignés) ! 😉
Maintenant que vous savez tout sur l’héritage, testez vos connaissances avec notre quiz de deuxième partie ! Dans la troisième partie, nous traiterons de la structuration de votre code et de la gestion des erreurs.