Utilisez des objets dans une collection
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 objetPersonne
, 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 ?
Utilisez le duck typing en toute sécurité
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. 😬
En voici une démonstration concrète !
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.
Documentez votre code
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).
Typez votre code
Python 3 vous fournit une bibliothèque detypes
, 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. 🤗
Adoptez la « programmation défensive »
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 (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.
Gérez des exceptions
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.
Une approche non interventionniste
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.
À vous de jouer : utilisez des objets dans les listes et les dictionnaires
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 :
Créez 1 utilisateur et un modérateur.
L’utilisateur crée un fil de discussion (vous pouvez inventer les messages).
Le modérateur répond dans ce fil.
L’utilisateur répond dans ce même fil par un message hors sujet❗
Le modérateur répond que c’est hors sujet, puis supprime le message de l’utilisateur et son dernier message.
L’utilisateur répond dans le fil en joignant une image.
N’oubliez pas d’afficher le fil de discussion sur le temps pour vérifier son état (les messages qui lui sont assignés) ! 😉
En résumé
Les objets peuvent être stockés dans des collections – tout comme n’importe quoi d’autre.
Python gère les types avec ce que l’on appelle le duck typing – si ça a un bec et cancane comme un canard, c’est probablement un canard.
Nous devons être particulièrement attentifs, lorsque nous utilisons les collections, à ne pas essayer d’accéder à des attributs qu’un objet ne possède pas.
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.