Règles de syntaxe
- Commentaire: #
- L’indentation permet de délimiter les bloc de code (par exemple pour une condition ou une boucle)
- Les chaines de caractères sont délimités par des quotes simples, des double quotes ou encore des triples double quotes
- Les triples double quotes (ex:
"""ma chaine""") permettent d’écrire une chaine sur plusieurs lignes et que chaque retour chariot sont retranscrit tel quel dans la chaine
- Les triples double quotes (ex:
- On écrit une ligne de code sur 2 lignes en terminant la première ligne par un backslash
- Après le nom de la fonction, on peut rajouter un commentaire (indenté bien sûr) avec des triple double quotes. Ce commentaire sera utilisé pour la documentation (notamment en faisant ‘‘help(NomFonction)’’
- Rendre un script executable directement
- Rajouter
#! /usr/bin/python3en 1ère ligne du fichier (Linux) : indique à Linux le chemin vers python pour exécuter le script - Rajouter
#-*-coding:utf-8-*-en 2e ligne pour indiquer l’encodage. utf-8 pour Linux, Latin-1 pour Windows
- Rajouter
Type annotation
Python est un langage fortement typé. Néanmoins le type des variables ou des paramètres ne doit pas forcément être spécifié. Le langage détermine le type à l’éxécution.
Mais pour des raisons de lisibilité ou de maintenabilité, une bonne pratique est d’indiquer les types attendus pour les variables, les paramètres ou les valeurs de retour des fonctions. Cette indication n’est pas du tout destiné à Python mais plutôt aux développeurs et éventuellement aux IDE qui peuvent aider à vérifier la cohérence du code.
La plupart des instructions utile est dispo dans la lib typing.
def my_func(param1: int, param2: str = "") -> float:
my_var: int = 5
...
Parmi les entrées existantes dans le module typing, on notera les éléments suivants particulièrement utiles:
- Any: n’importe quel type
- Callable: une fonction appelable.
Callable[[int, int], float]: Premier paramètre: la liste des arguments de la fonction, Second paramètre, le type de retour. Ici en l’occurence une fonction qui admet 2 int en entrée et retourne 1 float.
Instructions de base
- del: Détruit un objet
- Mots clé pour les tests:
and,oretnot - Bloc if
if condition: code else: code if condition code elif condition: code else: code - Bloc while
while condition: code - Bloc for
for element in sequence: code - Teste la présence d’un élément dans une séquence:
if element in sequence: - Affiche une variable à l’écran
print(arg1, arg2, ...) print("Hello World") print("Hello {}".format(name)) print("Hello World", end="\r") # spécifie le caractère de fin. Ici un retour chariot, donc la prochaine ligne sera écrite par dessus celle-ci.
Exceptions
try:
code
except:
traitement de l'exception
- Catch les exceptions correspondant au type indiqué. Note: on peut enchainer plusieurs except avec des type différents
- exemple d’exceptions: ValueError, NameError, TypeError, ZeroDivisionError
except typeException:
- Récupère l’exception: Crée un objet correspondant à l’exception levée
except typeException as objetException:
- Définir du code si aucune exception n’est levée: cela se fait avec le mot
elsetry: except ...: else: code - Exécuter du code après le bloc try, quelque soit le résultat. Ce code s’exécutera même si le
tryou lesexceptcontiennent unreturn. Cela se fait avec le mot cléfinally - Catcher une erreur mais ne rien faire: mot clé
passtry: code except typeException pass - Assertions: mot clé
assert. Lève une exceptionAssertionError.assert test - Lever une exception:
raise TypeException("message d'erreur")
Exceptions personnalisées
On peut définir nos propres classes d’exception
class MonException(Exception):
__init__(self, message):
self.message = message
__str__(self):
return self.message
- La classe d’exception personnalisée hérite généralement de
ExceptionouBaseException - Il faut à minima définir un constructeur et un méthode
__str__
Fonctions de base
| Fonction | Description |
|---|---|
| // | Division entière |
| type(var) | Donne le type de la variable |
| print(var1, var2) | Affiche une variable à l’écran |
| break | Abandonne la boucle en cours |
| continue | Abandonne l’itération de la boucle en cours et exécute immédiatement l’itération suivante |
| def nomFonction(param1, param2): | Définition d’une fonction. Note: on peut appeler la fonction fonction(2, 3) ou encore fonction(param1=2, param2=3). La 2e syntaxe permet d’identifier le paramètre si beaucoup de paramètres |
| def nomFonction(param1=val1, param2): | Affectation d’une valeur par défaut pour un paramètre |
| return param | Renvoie une valeur en retour d’une fonction |
| return ‘param1, param2 | Renvoie un tuple constitué de param1 et param2 |
| f=lambda param1, param2: code avec param1 et param2 sur une seule ligne | Crée une fonction lambda: une fonction très courte pouvant sécrire sur une seule ligne. Plus simple et plus rapide que d’écrire une fonction avec def |
| def fonction(*parametres) | Définit une fonction qui admet un nombre variable de paramètres. La variable ‘‘parametres’’ est en réalité un tuple qui contient la ou les variables passées à la fonction. |
| def fonction(**parametres) | Définit une fonction qui admet un nombre variable de paramètres nommés. La variable ‘‘parametres’’ est en réalité un dictionnaire. |
| def fonction(*parNonNommes, **parNommes) | Définit une fonction qui admet n’importe quoi comme paramètres. parNonNommes est un tuple qui contient tous les paramètres non nommés et parNommes est un dictionnaire qui contient tous les paramètres nommés |
| fonction(*myList) | Fourniture d’une liste en tant que paramètres d’une fonction |
| fonction(**myDict) | Fourniture d’un dictionnaire en tant que paramètres d’une fonction |
| fonction(*myList, **myDict) | Fourniture d’une liste et d’un dictionnaire en tant que paramètres d’une fonction |
| global maVar | Déclare la variable maVar comme variable globale. Cette instruction est placée dans une fonction et maVar est définie en dehors de toute fonction. Par convention, l’instruction global est placée immédiatement après le docstring |
Classe str
Les méthodes suivantes s’appliquent sur les objets de la classe str (chaines de caractères)
| Méthode | Description |
|---|---|
| upper() | Mettre en majuscules |
| lower() | Mettre en minuscules |
| strip() | Supprime les espaces inutiles en début et fin de chaine |
| “ma chaine {}”.format(var) | Remplace les occurences de {} par les variables indiquées dans format |
| “ma chaine {0}”.format(var) | Idem ci-dessus mais le numéro entre accolades précise l’ordre des variables. Les variables peuvent être placées dans n’importe quel ordre |
| “ma chaine {var1}”.format(var1=valeurVar) | Idem ci-dessus mais on peut donner des noms explicites aux variables à insérer |
| f”ma chaine {var1}” | f-string: la variable est directement insérée dans la chaine |
| ”#” * 20 | Répète la chaine autant de fois que spécifié. Possibilité avancées comme "##" * 5 + "." * 4 –> ##########.... |
| len(chaine) | Renvoie la longueur de la chaine |
| count | |
| find | |
| replace | |
| split(car) | Sépare une chaine en éléments délimités par ‘car |
| chaine.join(liste) | Crée une chaine contenant tous les éléments de liste séparés par chaine. Exemple: " ".join(['Hello', 'World', '!']) |
Listes
Une liste peut contenir des éléments de types différents.
| Méthode | Description |
|---|---|
| list() | Crée un liste |
| [] | Crée un liste |
| [a, b, …] | Crée un liste |
| maListe[i] | Accède au i-ème élément. On peut le modifier. |
| append(a) | Ajoute a à la fin de la liste |
| insert(pos, elem) | Insert l’élément elem à la position pos |
| liste1.extend(liste2) | Concaténation de listes |
| liste1 += liste2 | Concaténation de listes |
| liste.remove(i) | Supprime le i-ème élément de la liste |
| del(liste[i]) | Supprime le i-ème élément de la liste |
| [fonction(elem) for elem in sequence] | Parcourt chaque élément d’une liste et applique la fonction. Cette instruction ne modifie par la liste parcourue mais en crée une nouvelle (list comprehension) |
| [fonction(elem) for elem in sequence if condition] | Parcourt chaque élément d’une liste. Si la condition est vraie, la fonction est exécutée sur l’élément courant et le résultat est placé dans liste de retour. Si la condition est fausse, l’élément et ignoré. (list comprehension) |
| liste2 = liste1 | Crée une nouvelle référence sur une liste. Liste2 est en faite une référence à liste1. Les 2 listes pointent sur la même liste |
| liste2 = list(liste1) | Crée une nouvelle liste à partir des valeurs de liste1. Les 2 listes référencent des listes distinctes |
| liste1 == liste2 | Compare les contenus des listes |
| liste1 is liste2 | Compare les références des listes |
| liste1.sort() | Trie la liste. Ceci implique que python sache comparer les 2 objets. Cela peut passer par une surcharge de l’opérateur < |
| liste1.sort(key) | Trie la liste selon la clé indiquée (le paramètre key est une fonction). La clé peut être définie par une fonction lambda. Ex key=lambda x:x[n] si les objets sont des tuples ou encore key=lambda x:x.attr pour trier selon un attribut particulier de l’objet x |
| liste1.sort(reverse=True/False) | Tri inversé de la liste |
Trier des listes
Python permet des fonctions poussées permettant le tri de listes.
Se référer aux points suivants:
- sorted
- itemgetter
- attrgetter
Dictionnaires
Un dictionnaire est comme une liste mais les différents éléments sont référencés à l’aide d’une clé (ie une variables qui sert d’indice plutôt qu’un entier dans le cas des listes)
| Méthode | Description |
|---|---|
| dict() | Crée un dictionnaire |
| {} | Crée un dictionnaire |
| {“cle1”:val1, “cle2”:val2, …} | Crée un dictionnaire |
| monDico[“cle”] = valeur | Crée ou remplace la valeur d’un élément |
| monDico.pop(“cle”) | Retire l’élément correspondant à la clé et renvoie la valeur associée |
| monDico.keys() | Renvoie la liste des clés du dictionnaire (en réalité, ce n’est pas vraiment une liste mais se parcourt de la même manière |
| monDico.values() | Renvoie la liste des valeurs du dictionnaire (en réalité, ce n’est pas vraiment une liste mais se parcourt de la même manière |
| (cle, valeur) in monDico.items() | Récupère un tuple issu du dictionnaire. Utile surtout pour parcourir simultanément clé et valeur |
| {k:v for k,v in dict} | Dict comprehension: construit un dictionnaire à la volée en parcourant un itérable (mêmes possibilités que pour la list comprehension) |
Tuples
TODO
Regex
- import re
- Pour les regex, il faut échapper le backslash pour les caractère spéciaux (\n, \t, etc.).
\ndevient donc\\n. Pour simplifier l’écriture, on fait précéder la chaine de la lettre ‘r’ (pour raw). Ainsi “\n” devient r”\n”
| Fonction | Description |
|---|---|
| re.search(regex, chaine) | Cherche la regex dans chaine. Renvoi un objet si trouvé, None sinon |
| re.sub(regex, repl, chaine) | Remplace tous les patterns qui matchent la regex par repl. Note: Si on utilise des groupes de capture (avec les parenthèses), ils sont référés en tant que \n (avec n le numéro de groupe). Note: On peut nommer les groupes de capture: (?P<id>groupe) dans la regex. \g<id> dans la chaine de remplacement |
| re.compile(regex) | Compile une regex pour améliorer le temps d’exécution |
| regexCompilee.search(chaine) | Utilisation d’une regex compilée pour chercher la regex dans la chaine |
| regexCompilee.sub(repl, chaine) | Utilisation d’une regex compilée pour remplacer des patterns de regex dans la chaine |
Time
- import time
- import datetime
| Fonction | Description |
|---|---|
| time.time() | Retourne le timestamp courant |
| time.localtime([timestamp]) | Convertit un timestamp en struct_time, unobjet contenant l’année (tm_year), le mois (tm_mon), le jour du mois (tm_mday), le jour de la semaine (tm_wday), le jour de l’année (tm_yday), l’heure (tm_hour), les minutes (tm_min) et les secondes (tm_sec). Si aucun timestamp n’est précisé, la date et l’heure courante sont utilisées |
| time.mktime(struct_time) | Convertit un struct_time en timestamp |
| sleep(x) | Met le programme en pause pendant x secondes |
| time.strftime(format, struct_time) | Convertit une structure de temps en chaine de caractère formatée selon les indications données dans la chaine format. %Y année, %B nom du mois, %d jour du mois, %H heure, %M minutes, %S secondes, etc.) |
| datetime.date(annee, mois, jour) | Crée un objet date |
| datetime.time(heure, minutes, secondes, microsecondes, timeZoneInfo) | Crée un objet time |
| datetime.datetime(A, M, J, H, m, S, ms) | Crée un objet datetime |
| datetime.now() | Crée un objet datetime à l’horodate courante |
| datetime.fromtimestamp(timestamp) | Crée un objet datetime à partir d’un timestamp |
Fichiers
| Fonction | Description |
|---|---|
| monFichier = open(“fichier.txt”, ‘r’) | Ouvre un fichier en lecture. Il existe également les mode w, a, rb, wb et ab |
| monFichier.close() | Ferme le fichier |
| monFichier.read() | Lit l’intégralité du fichier |
| monFichier.write(“texte a ecrire”) | Ecrit dans le fichier |
| with open(“fichier.txt”, ‘r’) as monFichier: | Définit un gestionnaire de contexte. L’instruction open est exécutée et l’objet retourné est référencé en tant que variable monFichier. Après cette ligne de code, on écrit un bloc d’instructions. A la fin du bloc, monFichier est automatiquement fermé (que le bloc se soit terminé normalement ou suite à une exception) |
| pickle.Pickler | Permet de sauvegarder un objet dans un fichier. Requiert la classe *Pickler** du module ‘pickle’. Le fichier doit être ouvert en mode binaire. Cet objet enregistre sur le disque le dictionnaire des attributs de l’objet à sauvegarder. Ce dictionnaire peut être obtenu avec la méthode spéciale __dict__ de l’objet |
| pickle.Unpickler | Permet de restaurer un objet depuis un fichier. Requiert la classe Unpickler du module ‘pickle’. Le fichier doit être ouvert en mode binaire. |
Modules & packages
Modules
Un fichier python avec des fonctions est un module.
- Créer un fichier .py
- Pour linux: 1ère ligne:
#! /usr/bin/python3: Sert à indiquer le chemin vers l’exécutable pour interpréter le script. Utile si on veut exécuter directement le fichier. - 2ème ligne:
#-*- coding: utf-8 -*-(ou Latin-1): Sert à indiquer l’encodage du fichier - Ecrire ensuite la liste des fonctions implémentées par ce module
- Test du module: Rajouter une directive
if __name__ == "__main__":et écrire ensuite le code nécessaire au test (ou autre)- Ce code n’est pas exécuté lorsqu’on importe le module (la condition n’est pas vérifiée)
- Code code est exécuté lorsqu’on exécute directement le fichier
- Pour importer le module:
from nomModule import *: importe toutes les fonctions du module. Les fonctions s’appellent directement en utilisant le nom de la fonction.from nomModule import fonction: importe une seule fonction du moduleimport nomModule: importe le module mais les fonctions devront être appelées nomModule.fonction (revient un peu à importer un package)
Packages
Un package est un répertoire dans lequel on peut ranger des modules (des fichiers) ou d’autres packages (des sous-répertoires)
- Créer un répertoire portant le nom du package
- Placer dans ce répertoire tous les fichiers (modules) composant le package. Éventuellement créer des sous-répertoires pour faire des sous-packages.
- Créer le fichier d’initialisation du package: fichier
__init__.py
Pour importer le module:
from nomPackage.nomModule import *: importe toutes les fonctions du module du package. Les fonctions s’appellent directement en utilisant le nom de la fonction.from nomPackage.nomModule import fonction: importe une seule fonctionimport nomPackage.nomModule: importe le module mais les fonctions devront être appelées nomPackage.nomModule.fonction
On peut aussi mieux organiser le module en complétant le fichier __init__.py
from .fichier1 import fonction
from .fichier2 import fonction1, fonction2
__all__ = ["fonction", "fonction1", "fonction2"]
Ainsi, on n’a plus un package qui est constitué de fichiers et qui chacun contient des fonctions/classes. Mais on a un package qui expose des fonctions/classes. On n’a plus besoin de connaitre l’organisation interne.
- le point
.devant les noms de fichiers spécifient explicitement que l’on veut utiliser le fichier local - la liste
__all__liste les fonctions à exporter si l’utilisateur faitfrom module import *
Ainsi on pourra importer le module plus facilement, sans avoir besoin de connaitre les fichiers internes
from nomPackage import fonction: importe une seule fonction qui s’utilise directementfrom nomPackage import *fonction*: importe toutes les fonctions qui s’utilisent directementimport nomPackage: importe toutes les fonctions mais s’utilisentnomPackage.fonction
Orienté Objet
class NomClasse: # Création d'une classe
"""Ceci est le docstring de la classe.
Il s'agit de la documentation embarquée dans le code"""
attribut = valeur # Attribut de classe: attribut commun à tous les objets (equivalent aux attributs statiques en C++)
def __init__(self, par1, ...): # Constructeur
self.attribut1 = par1
...
def __del__(self): # Destructeur
code
def methode1(self, param1, ...):
code de la méthode
def methodeDeClasse(cls, params):
code
methodeDeClasse = classmethod(methodeDeClasse)
def methodeStatique(params):
code
methodeStatique = staticmethod(methodeStatique)
- le mot clé class permet de début la déclaration d’une classe
- les attributs déclarés en dehors des méthodes sont des attributs de classe (dépendant de la classe et non de l’objet)
- Ces attributs s’accèdent avec NomClasse.attribut
- le constructeur est nommé
__init__ - le destructeur est nommé
__del__ - le 1er paramètre de toutes les méthodes (constructeur compris) est le paramètre self ** Les méthodes sont attachées à la classe et non à l’objet. Lorsqu’on appelle la méthode, il est donc utile de lui passer l’objet sur lequel est appelé la méthode.
- En python, on peut accéder directement aux attributs. Pas besoin de getter/setter
- Les méthodes de classe
- 1er paramètre: cls. C’est en quelques sorte un pointeur sur la classe
- travaille sur les attributs de classe avec cls.attr
- requiert l’instruction classmethod
- sont appelées avec NomClasse.methode ou peuvent être appelées sur un objet de la classe
- Méthode statique
- paramètres ne contenant ni self, ni cls
- requiert l’instruction staticmethod
- Ne travaille ni sur les attributs d’un objet, ni sur les attributs de classe
Héritage
class ClasseFille(ClasseMere1, ClasseMere2):
⚠️ Le constructeur de la classe mère doit être appelé explicitement. Sinon, il n’est pas appelé et tous les attributs présents dans le constructeur de la classe mère mais pas celui de la classe fille ne seront pas créés
class ClasseFille(ClasseMere):
def __init__(self, args):
super().__init__(args)
# Other initialisations
def myMethod(self, args):
super().myMethod(args)
# Alternative syntax:
# ClasseMere.myMethod(self, args)
issubclass(ClasseFille, ClasseMere): Renvoie true si ClasseFille hérite de ClasseMereisinstance(monObjet, MaClasse): Renvoie true si monObjet est un instance de MaClasse (ou d’une classe dérivée)
Propriétés
Les propriétés permettent de faire de l’encapsulation avec python.
class NomClasse:
def __init__(self):
_attribut = ''valeur''
def _getAttribut():
return _attribut
def _setAttribut(val):
_attribut = val
attribut = property(_getAttribut, _setAttribut)
- Par convention, les attributs et méthodes qui commencent par un underscore (_) ne doivent pas être accédées de l’extérieur
- On définit un attribut (que l’on veut privé) qui commence par _
- On définit un getter et un setter pour cet attribut. Tous 2 commencent par un _
- On définit une propriété qui s’appelle comme l’attribut mais sans le _.
- On rattache le getter et le setter à la propriété avec le mot clé property
- property accepte jusqu’à 4 paramètres facultatifs:
- la méthode appelée lorsqu’on veut accéder à l’attribut
- la méthode appelée lorsqu’on veut modifier l’attribut
- la méthode appelée lorsqu’on veut supprimer l’attribut
- la méthode lorsqu’on demande l’aide associée à l’attribut
dataclass
Le module dataclasses fournit un décorateur très utiles pour les classes “conteneurs”, çàd qui ne contiennent que des données et ne possèdent pas de méthodes.
from dataclasses import dataclass
@dataclass
class MyContainer:
name: str
item1: int
item2: float
- la méthode
__repr__est automatiquement générée. Ce qui signifie qu’une demande d’affichage d’un objet de cette classe sera lisible, contrairement à l’affichage d’un objet d’une classe standard.
Méthodes spéciales (dunder methods - for double underscore)
| Méthode | Description |
|---|---|
__repr__(self) |
Méthode appelée lorsqu’on demande à afficher l’objet depuis l’éditeur. Elle est également appelée si on appelle la commande print sur l’objet et que la méthode __str__ n’est pas définie. return type: str |
__str__(self) |
Méthode appelée lorsqu’on exécuter la commande print sur l’objet ou lorsqu’on veut convertir l’objet en string. return type: str |
__getattr__(self, nomAttr) |
Méthode appelée si on tente d’accéder à un attribut inexistant. return type: ce qu’on veut comme valeur retour d’un paramètre inexistant |
__setattr__(self, nomAttr, valeur) |
Méthode appelée lorsqu’on modifie un attribut. Cette méthode se substitue à la méthode par défaut (héritée de la classe object. Pour réellement modifier le valeur de lattribut, il faut explicitement appeler la méthode de la classe mère object.__setattr__(self, nomAttr, valeur) |
__delattr__(self, nomAttr) |
Méthode appelée lorsqu’on supprimer un attribut. Pour réellement supprimer l’attribut, appeler la méthode de la classe mère object.__delattr__(self, nomAttr) |
__getitem__(self, index) |
Méthode appelée lorsqu’on veut accéder à un élément d’un conteneur. Par exemple lorsqu’on fait monObjet[index] |
__setitem__(self, index, valeur) |
Méthode appelée lorsqu’on veut modifier un élément d’un conteneur. Par exemple lorsqu’on fait monObjet[index] = valeur |
__delitem__(self, index) |
Méthode appelée lorsqu’on veut supprimer un élément d’un conteneur. Par exemple lorsqu’on fait del monObjet[index |
__contains__(self, valeur) |
Méthode appelée lorsqu’on écrit valeur in monObjet. return type: True/False |
__len__(self) |
Méthode appelée lorsqu’on écrit len(monObjet). return type: int |
__add__(self, valeur) |
Surcharge de l’opérateur +. Cette méthode est appelée lorsqu’on écrit monObjet + valeur. Note: Charge à cette méthode de gérer les différents types pour valeur |
__sub__ |
Surcharge de l’opérateur - |
__mul__ |
Surcharge de l’opérateur * |
__truediv__ |
Surcharge de l’opérateur / |
__floordiv__ |
Surcharge de l’opérateur // |
__mod__ |
Surcharge de l’opérateur % |
__pow__ |
Surcharge de l’opérateur ** (puissance) |
__radd__ |
Lorsqu’on écrit valeur + monObjet, cela suppose que la classe de valeur sache comment additionner monObjet, ce qui n’est pas le cas si valeur est d’un type standard. Dans ce cas, pyhton tente d’appeler la méthode __radd__ (qui revient à monObjet + valeur). en général: return monObjet + valeur. Permet notamment d’appeler l’opération sum sur une collection d’objets. Dans ce cas, le premier appel correspond à 0 + premierObjetDeLaListe. Note: s’applique pour toutes les méthodes mathématiques ci-dessus |
__iadd__ |
Surcharge de l’opérateur +=. Note: valable pour tous les opérateurs (-=, =, etc.). Il suffit de rajouter un *i |
__eq__(self, objetAComparer) |
Surcharge de l’opérateur ==. return type: True/False |
__ne__(self, objetAComparer) |
Surcharge de l’opérateur != . return type: True/False |
__gt__(self, objetAComparer) |
Surcharge de l’opérateur >. return type: True/False. Note: Si python n’arrive pas a exécuter objet1 < objet2, il va automatiquement tenter d’exécuter l’opération inverse: objet2 >= objet1 |
__ge__(self, objetAComparer) |
Surcharge de l’opérateur >=/. return type: True/False |
__lt__(self, objetAComparer) |
Surcharge de l’opérateur <. return type: True/False |
__le__(self, objetAComparer) |
Surcharge de l’opérateur <=. return type: True/False |
__getstate__(self) |
Cette méthode est appelée juste avant l’enregistrement de l’objet à l’aide de la classe Pickle. Cette méthode sert à récupérer le dictionnaire des attributs de l’objet. Il faut donc obtenir le dictionnaire de l’objet avec dict(self, __dict__), éventuellement modifier ce dictionnaire et enfin de renvoyer en tant que donnée retour de la méthode. |
__setstate__(self, dictAttr) |
Cette méthode est appelée juste après le chargement d’un objet à l’aide de la classe Unpickle. Cette méthode doit se charger de mettre à jour le dictionnaire des attributs de l’objet: self.__dict__ = dictAttr |
__enter__(self) |
Cette méthode est appelée lors de la création d’un Context Manager: with MyObj(param) as o:. Cette méthode doit faire return self car c’est l’objet qui sera passé avec la commande as |
__exit__(self, exc_type, exc_value, traceback) |
A la fin de la portée du context manager. exc_type et exc_value sont utilisés s’il y a eu une exception. Permet de détecter s’il faut fermer le context manager différement en cas d’exception. |
__iter__(self) |
Utilisé pour les objets itérables. cf. section Itérateurs |
__next__(self) |
Utilisé pour les objets itérables. cf. section Itérateurs |
Itérateurs
Lorsqu’on exécuter le code for elem in sequence, python effectue les actions suivantes:
- Il appelle la fonction iter(sequence) qui va elle-même appeler la méthode
sequence.__iter__()- Cette méthode renvoie un itérateur sur la séquence.
- Il appelle successivement la fonction
next(iterateur), ce qui revient à appeler la méthodeiterateur.__next__.- Cette méthode passe à l’élément suivant et renvoie sa valeur
- Lorsque la fin de la séquence est atteinte, cette méthode lève une exception StopIteration
⚠️ Important: Si la méthode
__iter__contient une instructionyield, ce sont les générateurs qui sont utilisés au lieu de l’itérateur (cf. ci-dessous)
Si on veut créer une classe qui peut être parcourue avec l’instruction for, il faut:
- définir la méthode spéciale
__iter__. Cette méthode devra créer et retourner un itérateur sur la classe - Créer une classe itérateur capable d’itérer sur la classe à parcourir
- Cette classe implémente à minima la méthode
__next__- cette méthode renvoie l’élément suivant à chaque appel
- cette méthode lève une exception StopIteration lorsque la fin de la séquence est atteinte
- Cette classe implémente à minima la méthode
Générateur
Avec le mot clé yield, la fonction est suspendue et renvoie la valeur spécifiée après l’instruction yield. La fonction poursuit son exécution au prochain appel de next. Lorsque la fonction arrive à la fin, l’exception StopIteration est automatiquement levée
✏️ Note: Si la méthode
__iter__contient un instruction yield, c’est cette méthode qui est appelée pour chaque élément de la séquence. On peut alors se passer de définir une classe Itérateur.
Note: On peut interagir avec le générateur:
- On peut stopper la boucle du générateur avec la méthode
close() - On peut passer une valeur au générateur avec la méthode
send()
Décorateurs
Les décorateurs permettent de modifier ou améliorer le comportement d’une fonction/méthode. Il s’agit d’une fonction qui s’exécute “autour” d’une fonction/méthode/classe: elle peut exécuter du code avant/après mais aussi modifier les paramètres.
On ajoute un décorateur à une fonction en ajoutant simplement @decorator avant l’implémentation de la fonction.
Implémentation
Définition du décorateur
def my_decorator(func):
def wrapper(*args, **kwargs):
print(">>> Starting function")
# ici on peut modifier les args si on veut
func(*args, **kwargs)
print(">>> Function finished")
return wrapper
Utilisation
@my_decorator
def my_function(arg1, arg2):
# implem
Exemple de décorateur de méthode
def my_decorator(func):
def wrapper(self, *args, **kwargs):
res = func(self, *args, **kwargs)
return res
return wrapper
Décorateurs utiles à connaitre
staticmethod: méthode statique de classeclassmethod: méthode de classeproperty: définit une méthode en tant que propriété. Utile pour encapsuler les attributs et maitriser leur accès à l’aide de getter et setter<nomProperty>.setter: définit une méthode en tant que setter pour la propriété indiquée.
deprecated: indique une fonction comme dépréciée (nécessitefrom warnings import deprecated)
Interactions avec l’OS
- Signaux: le programme python est capable d’émettre ou de réceptionner des signaux et d’y associer un traitement spécifique
- le signal SIGINT est émis lorsqu’on demande au programme de se terminer
- Récupérer les arguments de la ligne de commande
- sys.argv fournit la liste des arguments de la ligne de commande (le 1er élément est le nom du programme)
- le module
argparseest utile pour extraire les options d’exécution (ex: -h ou –help)
- Exécuter une commande système
os.system("commande")
Réseau
- import socket
Côté serveur
| Instruction | Description |
|---|---|
| maConnexion = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | Création d’une socket |
| maConnexion.bind((hote, port)) | Affecte la connexion à l’interface réseau (nom d’hôte et numéro de port). Mettre hote = ‘’ |
| maConnexion.listen(backlog) | Placer la connexion en mode d’écoute. Backlog est un chiffre qui indique combien de client peuvent être simultanément en attente de connexion (généralement réglé à 5) |
| (connexion, infos) = maConnexion.accept() | Accepte une demande de connexion d’un client. Retourne 2 infos: la connexion qui est un socket connecté au client et un tuple contenant les infos de connexion (adresse IP, port de sortie). Note: cette méthode est bloquante si aucun client n’a fait de demande de connexion |
| maConnexion.send(b’Message à envoyer’) | Envoie le message indiqué. important: Le message est en octets et non une string, d’où le b devant la chaine |
| msg = maConnexion.recv(tailleBuffer) | Réceptionne un message. Le message reçu est limité à la taille indiquée. |
| connexion.close() | Ferme la connexion |
Côté client
| Instruction | Description |
|---|---|
| maConnexion = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | Création d’une socket |
| maConnexion.connect((adresseIp, port)) | Connexion au serveur ayant l’adresse Ip indiquée au port précisé |
| maConnexion.send(b’Message à envoyer’) | Envoie le message indiqué. important: Le message est en octets et non une string, d’où le b devant la chaine |
| msg = maConnexion.recv(tailleBuffer) | Réceptionne un message. Le message reçu est limité à la taille indiquée. |
| connexion.close() | Ferme la connexion |
Autres
Les méthodes accept et recv sont bloquantes. Pour éviter de bloquer le programme et permettre la gestion de plusieurs clients simultanément, on peut utiliser le module select
- import select
- rlist, wlist, wlist = select.select(rlist, wlist, xlist, timeout)
- les arguments fournis à la fonction sont respectivement la liste des sockets en attente de réception, la liste des sockets en attente d’écriture et la liste des sockets en attente d’erreur.
- La fonction parcourt chaque socket des 3 listes et regarde si l’un d’eux a des données à traiter
- La fonction retourne au plus tard après écoulement du timeout et le programme peut se poursuivre
- Les 3 listes en retour sont respectivement la liste des socket ayant reçu des données, la liste des sockets ayant émis des données et la liste des sockets ayant traité des erreurs. Ces 3 listes n’ont rien à voir avec les 3 listes passées en argument
- On peut ensuite parcourir chaque socket des listes de retour et traiter les données
Threads
- from threading import Thread, RLock
- Créer une classe qui hérite Thread
- Dans le constructeur ne pas oublier d’appeler le constructeur de la classe mère:
Thread.__init__(self) - Redéfinir la méthode
run - Démarrer le Thread avec la méthode
start - Attendre l’arrêt d’un Thread avec la méthode
join
Verrouiller une ressource avec RLock:
verrou = RLock()
...
with verrou:
utilisation d'une ressource protégée
Tests unitaires
Module unittest
⚠️ Le module unittest est aujourd’hui avantageusement remplacé par pytest
- Placer les TU d’une classe dans un module dédié qui doit impérativement s’appeler test*
- Importer le module
unittest - Créer une classe qui hérite de
unittest.TestCase - Créer une méthode par test. La méthode doit impérativement commencer par le mot test
- La méthode
setUp(self)est appelée avant chaque test - La méthode
tearDown(self)est appelée après chaque test - Les méthodes de test contiennent des assertions pour vérifier l’objet des tests
import unittest
class MaClasseTest(unittest.TestCase):
def setUp(self):
# initialisation
def test_cas1(self):
assert(...)
def test_cas2(self):
assert(...)
def tearDown(self):
# remise en état
Les TU peuvent être appelés:
unittest.main()dans le module de test- commande
python -m inittestdans le dossier du module
| Assertions de test |
|---|
| assertEqual(a, b) |
| assertNotEqual(a, b) |
| assertTrue(a) |
| assertFalse(a) |
| assertIs(a, b) |
| assertIsNot(a, b) |
| assertIsNone(a) |
| assertIsNotNone(a) |
| assertIn(a, b) |
| assertNotIn(a, b) |
| assertIsInstance(a, b) |
| assertIsNotInstance(a, b) |
| assertRaises(exception, fonction, arguments) |
La dernière assertion vérifie que l’appel de la fonction lève l’exception attendue
Pytest
Pytest permet de découvrir tout seul tous les tests présents dans le projet si les règles suivantes sont respectées
- les fichiers de test doivent commencer par
test_ - les fonctions de test doivent commencer par
test_ - les classes de test (si ou souhaite regrouper les cas de test par familles) doivent commencer par
Test
Pour lancer les tests:
- pytest: lancer tous les tests
- pytest -v : mode verbose
Marker
Les marker permettent d’associer des tags aux tests pour pouvoir n’exécuter qu’une sous-catégorie de tests (exemple: des tests lents, des tests pour une fonctionnalité données, etc.)
@pytest.mark.slow
@pytest.mark.feature1
def test_my_feature():
...
- pytest -m “slow”: ne lance que les tests lents
- pytest -m “not slow”: lance tous les tests sauf les lents
- pytest -m “feature1 and not slow”: lance uniquement les tests non lents pour la feature1
Les markers doivent être déclarés dans un fichier de configuration pytest.ini
[pytest]
marker =
slow: Tests lents
feature1: Tests de la feature trucmuche
...
Marker spéciaux
pytest.mark.skip(reason="pourquoi"): Skip le test. La raison est optionnellepytest.mark.xfail(reason="pourquoi"): On s’attend à ce que le test échoue
Parametrize
Plutôt que de multiplier les fonctions de test ou de faire des itérations à l’intérieur des tests, on peut les paramétrer avec le décorateur parametrize
@pytest.mark.parametrize("a, b, result", [(1, 1, 2), (100, 200, 300), (-1, 1, 0)])
def test_addition(a, b, result):
assert (a+b == result)
On indique directement tous les cas de tests pour une même fonction de test dans le décorateur.
- le 1er paramètre est une string qui contient les noms de tous les paramètres qui seront passés à la fonction. Les noms doivent correspondre
- le 2e paramètre est une liste de tuples.
- chaque tuple correspond à 1 cas de test
- les valeurs du tuple correspondent aux valeurs des paramètres
- 3e paramètre optionnel:
ids=["Test case 1", "Test case 2", etc.]. Pour donner un manuellement un ID à chaque cas de test. Peut être utile pour s’y retrouver parmi tous les tests
Dans l’exemple ci-dessus, la fontion de test génère l’exécution de 3 tests. Ces 3 tests possèdent les même caractéristiques.
Si on veut générer des caractéristiques différentes, il faut utiliser pytest.param
@pytest.mark.parametrize("a, b, result", [
(1, 1, 2),
pytest.param(2, 2, 5, marks=pytest.mark.xfail),
pytest.param(-1, -1, -2, marks=pytest.mark.skip, id="Negative values")])
def test_addition(a, b, result):
assert (a+b == result)
Ainsi, on peut définir des markers différents selon les cas tests et donc filtrer les tests avec la puissance des markers.
Librairies utiles
Une sélection de librairies à connaitre en Python
- argparse: Gérer facilement les arguments passés en entrée des scripts
- configparser: Gérer facilement des fichiers de configuration (fichiers .ini)
- logging: Créer facilement des fichiers de log (avec niveau de détails des logs, timestamp, etc.)
- docx: Manipuler des fichiers Microsoft Word
- itertools: Outils d’itération supers puissants: accumulation, création de toutes les combinaisons possibles entre plusieurs listes, etc.
- abc: Abstract Base Class, notamment pour la classe de base
ABCqui est abstraite et pour les décorateurs@abstractmethodpour définir une méthode abstraite à surcharger (cf. article ici) - dataclasses: Pour définir une classe qui ne fait que du stockage de donnée et pas du code comportemental. On utilisera surtout le décorateur
@dataclassainsi que la fonctionfield(incidence sur la valeur par défaut, la présence du champ dans la méthode__repr__ou un constructeur par défaut -default_factory) - functools: Fonctions d’ordre supérieur pour s’appliquer sur des fonctions. Notamment pour le décorateur
@cachequi permet de mettre en cache le résultat d’une fonction qui serait appelée beaucoup de fois ou qui prend du temps. Ainsi, le cache est utilisé plutôt que d’exécuter la fonction à chaque fois.
Virtual environment
Les environnements virtuels permettent de spécifier des packages différents à des versions données pour un projet particulier. L’intérêt est que chaque projet peut posséder ses propres dépendances sans se soucier des projets annexes ou encore de l’installation système. Le tout étant portable d’un poste de travail à un autre très simplement.
- pthon -m venv .venv: crée un environnement virtuel. Tous les fichiers de configuration seront placés dans le dossier
.venv. On peut spécifier un nom différent pour ce dossier - source .venv/bin/activate: active l’environnement virtuel
- à partir de maintenant, tous les packages installés ne seront disponibles que pour l’environnement actif.
- deactivate: désactive l’environnement virtuel
- pip freeze > requirements.txt: dump la liste de tous les packages installés ainsi que leur version dans un fichier requirements.txt
- pip install -r requirements.txt: installe tous les packages spécifiés dans le fichier requirements.txt