Formation Python

Formation Python - avancée

Année 2023

_

Formation Python

Jour 2 & 3 - Compléments

Voici les thèmes en compléments:

  • La gestion des erreurs.
  • Les notations en compréhension.
  • Les annotations de types.
  • Les bonnes pratiques.
Formation Python

La gestion des erreurs

En python, la gestion des erreurs est basée sur le mécanisme des exceptions. Lorsqu'une erreur intervient, un message spécifique s'affiche en indiquant:

  • le nom de l'erreur
  • la localisation de l'erreur avec:
    • un nom de fichier, un numéro de ligne et un nom de fonction
  • la liste de tous les appels des fonctions précédents l'erreur
Traceback (most recent call last):
  File "C:\Users\robert\Desktop\hanoi_towers.py", line 11, in <module>
    move(n, 'A', 'B', 'C')
  File "C:\Users\robert\Desktop\hanoi_towers.py", line 5, in move
    move(disk-1, _from, _aux, _to)
  File "C:\Users\robert\Desktop\hanoi_towers.py", line 5, in move
    move(disk-1, _from, _aux, _to)
  File "C:\Users\robert\Desktop\hanoi_towers.py", line 7, in move
    uu
NameError: name 'uu' is not defined

Il faut lire ces informations en sens inverse, du bas vers le haut

Ici, dans notre exemple, le script python s'est arrêté. Cependant le programmeur peut décider si programme continue, ou pas.

Formation Python

Le mécanisme des exceptions

Ce mécanisme est basé sur le tryptique de bloc try, except, else. Les 2 premiers blocs sont obligatoires. Il peut y avoir plusieurs blocs except, mais un seul bloc try et un seul bloc else, toujours en dernière position. Voici un exemple:

try:
  a = 10
  b = "20"
  a + b
except Erreur1:
  print('Il y a soucis sur Erreur1')

except Erreur2:
  print('Il y a soucis sur Erreur2')

else:
  print('Pas d\'exception levée')
Formation Python

Le bloc try

Ce bloc permet de délimiter un ensemble d'instructions qui seront sous la surveillance
du mécanisme de gestion d'erreur.

try:
	a = 10
	a += 'yo'
	z = u
	try:
		a === 10
	except ...:
		pass
except ...:
		pass

Ici les 3 lignes du 1° bloc try sont surveillées par le mécanisme d'erreur, ainsi que le bloc try/except imbriqué.

Formation Python

Le bloc except

Ce bloc sert à intercepter une erreur. Comme plusieurs blocs successifs sont possibles, l'ordre des blocs est important.

Les exceptions sont basées sur le mécanisme de l'héritage. L'exception de référence BaseException est la plus générique (classe mère ou de référence). Toutes les exceptions en Python dérivent de celle-ci. Donc en positionnant un bloc except BaseException en premier, toute exception, quelle soit très spécifique ou pas, sera interceptée.

L'exception la 'plus générale' doit toujours être positionnée en dernier.

import os
print('avant le try')
try:
	os.listdir('/Users/Albert/Desktop')
except OSError as e:
	print(type(e), str(e))
except BaseException as e:
	print(type(e), str(e))
	raise # ici je propage l'erreur vers l'appelant
print('ici je continue')

L'expression as e dans le bloc except ... permet de récupérer l'exception dans une variable.

Formation Python

Une fois que l'exeption a été interceptée, c'est au programmeur de décider de la suite à donner:

  • soit le programme continue après un traitement spécifique ou pas,
  • soit l'erreur est renvoyée à l'appelant en propageant l'erreur par la mise en place d'une instruction raise dans le bloc except. Dans ce cas, si aucune exception n'est indiquée après le raise, c'est l'exception est cours qui est transmise.

A noter: dans le traitement d'une exception, un appel à raise OtherException est tout à fait possible. L'exception à l'origine de l'erreur, peut être précisée avec l'instruction from comme ceci:

try:
	1/0
except ArithmeticError as e:
	print(type(e), str(e))
	raise MyOwnError('...') from e # ici je propage l'erreur `MyOwnError` vers l'appelant avec une référence à  `ArithmeticError`

Le bloc else

Ce bloc optionnel unique est appelé quand il n'y a pas eu d'exception levée.

Formation Python

Lever une exception

La levée d'exception se fait par l'appel à l'instruction raise, suivi par un objet dont la classe correspond à l'erreur. Toute exception en Python est du format XxxxxxxxError, comme par exemple:

  • ValueError
  • TypeError
  • IndexError
  • ImportError

L'appel à une instruction raise invoque le mécanisme d'erreur et interrompt l'exécution de la fonction dans laquelle est située cette instruction (équivalent à une instruction return.

def racine_carre(val):
	if not isinstance(val, (int, float)):
		# leve l'exception TypeError et quitte racine_carre
		raise TypeError('"val" doit être un nombre entier ou flottant')
	if val < 0:
		# leve l'exception ValueError et quitte racine_carre
		raise ValueError('"val "doit être un nombre positif ou nul')

	return sqrt(val)
Formation Python

Créer ses propres exceptions

Pour créer ses propres exceptions il suffit de dériver d'une des classes déjà existantes

class PresentationError(Exception):
	pass

def test():
	"""
	"""
	raise PresentationError("Sample from test")

try:
	test()
except PresentationError as e:
	print(type(e), str(e))

Annexe1

Voir Les exceptions de Python

Formation Python

Les notations en compréhension

La notation en compréhension est un mécanisme qui permet de contruire en une ligne un conteneur, une collection, une séquence ou un générateur.

Création d'un conteneur, d'une collection ou d'une séquence

Si je veux créer une liste de nombres négatifs à partir d'une liste référence, je vais devoir:

  • Créer une liste vide
  • Parcourir la liste de référence
  • Et ajouter à la liste vide tous les éléments qui répondent aux critères de sélection
    exemple:
    lst = [12, -5, 0, 26, 78, -6]
    neg = []
    for item in lst:
        if item < 0:
            neg.append(item)
    print(neg)
Formation Python

Ou bien j'utilise une notation en compréhension pour la création de la liste.

    neg = [item for item in lst if item < 0]
    #           +---- loop ---+
    #                           +-alternative-+
    #     +keep+
    print(neg)

Ces notations en compréhension peuvent s'utiliser pour la production d'objet mutable comme:

  • list
  • dict
  • set
Formation Python

Le corps de la notation en compréhension peut contenir comme instruction:

  • une ou plusieurs boucles (Attention à la lisibité).
  • un test avec plusieurs conditions liées par des opérateurs.

Les valeurs produites peuvent être de n'importe quel type, valeur simple ou élaborée comme des tuples, listes ou autres objets.

Construction de liste.

# liste de tuple avec restriction 
l = [(item, item+2) for item in range(0, 100, 3) if item % 7 == 2]

# Liste de tuple avec boucle imbriquée ou itertools.product
l1 = [(a, b) for a in range(3) for b in 'ABC']
l1i = [(a, b, c) for a, b, c in itertools.product(range(3), 'ABC', (True, False, None))]

Formation Python

Construction de dictionnaire.

# dictionnaire simple 
d = {i:str(i) for i in range(5)}

# dictionnaire inversé
d_inv = {v:k for k, v in d.items()}

# dictionnaire à partir d'un dictionnaire avec une restriction
d2 = {k:d[k] for k in d if k % 2 == 0}

Construction d'ensemble.


# set à partir d'une série de chiffre - 7 valeurs prduites mais seules 4 conservées au niveau du set
s = {x**2 for x in range(-3, 4, 1)}

Formation Python

Les annotations de types: introduction

Ce système permet d'indiquer à titre informatif, les 'types' ou 'regroupements de 'types' attendus pour:

  • les paramètres d'une fonction, d'une méthode,
  • les valeurs retournées d'une fonction, d'une méthode,
  • les variables locales et globales, attributs d'objet, de classe.

Voici un exemple pour la fonction racine_carre vue dans le chapître sur les exceptions.

from typing import Union

def racine_carre(val: Union[int, float]) -> float:
	"""initialement: `def racine_carre(val):`
	etc ....."""
	if not isinstance(val, (int, float)):
		# leve l'exception TypeError et quitte racine_carre
		raise TypeError('"val" doit être un nombre entier ou flottant')
	if val < 0:
		# leve l'exception ValueError et quitte racine_carre
		raise ValueError('"val "doit être un nombre positif ou nul')

	return sqrt(val)

A noter: les annotations de type ont été introduites à partir de la version 3.5 de Python.

Formation Python

La bibiothèque interne typing

from typing import Callable, Union, TypeVar

Cette bibliothèque contient les types élaborés renommés, les types génériques, les combinaisons, les alias de type ou des nouveaux types qui seront utilisés pour définir les annotations. Par exemple:

  • Tuple, Dict, List,
  • Sequence, Iterable, Collection, Callable, Generator, Coroutine, Iterator
  • Optional, Union, Any,
  • TypeVar, Alias de type.

Voir la bibiothèque typing

Quelques exemples d'annotations

from typing import Collection, List
# function
def somme_des_chiffres(collec: Collection) -> float:
  """Somme des nombres de la collection"""
  return sum(item for item in collec if isinstance(item, (int, float)))
# Alias
vector = List[float]

A noter: les annotations sont stockées dans la variable interne __annotations__ rattachée à la fonction.

Formation Python

Les bonnes pratiques

Les bonnes pratiques en Python constitue un ensemble de règles que les développeurs s'attèlent à respecter (c'est pas toujours facile ;-)). Lors des revues de codes, les développeurs y font toujours référence.

La PEP8

Les bonnes pratiques en Python sont résumées dans une document nommé PEP8 et donc voici un résumé. Ici sont listés tous les cas concernant:

  • le nommage des variables, classes, fonctions, méthodes, constantes,
  • la ponctuation, les espaces, les blocs, les sauts de ligne,
  • la longueur d'une ligne (80 caractères).

Les commentaires

Les commentaires en Python s'utlisent principalement sous 2 formes:

  • les commentaires en bloc, les docstring avec les trytiques de quotes """ceci est une docstring""" sur les fonctions, les classes, les méthodes,
  • les commentaires de ligne, avec le # comme délimiteur de début.
Formation Python

Les linters

Ces outils de contrôle de code peuvent être installés et utilisés en ligne de commande via la commande python -m pip install <outil>. La plupart des IDE (PyCharm, Spyder, VSCode) propose aussi leur installation et exécution (souvent automatique) à travers des extensions. Ils sont appelable via un programme créé lors de l'installation.

Il existe plusieurs outils pour vérifier, relire et proposer des améliorations sur votre code, parmi les plus utilisés voici (A cette heure ci ...):

  • black
  • flake8
  • pylint
  • mypy
  • bandit
  • coverage
Formation Python

Black

Cet outil est un 'formateur' de code Python, c'est à dire qu'il repère les lignes de code qui ne respectent pas les règles de la PEP8. Il propose une ré-écriture.

Attention, cet outil modifie votre code, uniquement sur la mise en forme.

La bibliothèque

Formation Python

Flake8

Cet outil analyse le code d'un fichier Python, repère les lignes de code qui ne respectent pas la PEP8, et fait une analyse statique sur certains types d'anomalies comme:

  • éléments non utilisés comme des variables, des paramètres, des bibliothèques,
  • noms de fonctions non trouvés.

La bibliothèque
La documentation

Formation Python

Pylint

Cet outil reprend les vérifications de flake8 et en ajoute d'autres autour des attributs, des erreurs de syntaxes, vérifient la documentation sur les fonctions, classes et méthodes.

Le gros plus: Pylint repère les duplications de code ;-)

La bibliothèque
La documentation

Formation Python

Mypy

Cet outil analyse les annotations de type mises en oeuvre et vérifient statiquement si les paramètres passés, les valeurs retournées, correspondent aux types déclarés dans les annotations.

La bibliothèque
La documentation

Formation Python

Bandit

Cet outil permet de rechercher des vulnérabilités sur le code Python.

La documentation

Formation Python

Coverage

Cet outil permet de faire de la couverture de code, c'est à dire de vérifier que toutes les parties d'un code sont bien utilisées (nécessaires).

La documentation

Les linters dans vscode

Réglages des linters

Formation Python

Le Zen de Python

Exécuter la ligne suivante python3.8 -c 'import this'

  • Explicit is better than implicit.
  • Simple is better than complex.
  • Complex is better than * complicated.
  • Flat is better than nested.
  • Sparse is better than dense.
  • Readability counts.
  • Special cases aren't special enough to break the rules.
  • Although practicality beats purity.
  • Errors should never pass silently.
  • Unless explicitly silenced.
  • In the face of ambiguity, refuse the temptation to guess.
  • There should be one-- and preferably only one --obvious way to do it.
  • Although that way may not be obvious at first unless you're Dutch.
  • Now is better than never.
  • Although never is often better than right now.
  • If the implementation is hard to explain, it's a bad idea.
  • If the implementation is easy to explain, it may be a good idea.
  • Namespaces are one honking great idea -- let's do more of those!
Formation Python

Des questions ......

  • A jeudi et vendredi pour la programmation Objet en Python
  • Bonne fin de journée