FORMATION PYTHON

Formation Python

Année 2023

_

FORMATION PYTHON

Les décorateurs

Les décorateurs correpondent à des traitements enrobants sur des fonctions ou des classes. C'est à dire que ni la fonction ou la classe décorée ne sont modifiées. Par contre, la mise en place du décorateur permet d'agir avant l'appel réel à la fonction décorée et également au retour de la fonction décorée.

Un décorateur peut être écrit sous la forme d'une fonction ou d'une classe, et peut avoir ou pas des paramètres

Il peut s'appliquer soit à:

  • une fonction
  • une classe, principalement sur ses méthodes

Un décorateur se met en place en 2 temps:

  1. Décorer la fonction via l'opérateur Python @ collé au nom du décorateur
  2. L'appel à la fonction décorée
FORMATION PYTHON

Les décorateurs pour les fonctions

Voici le schéma classique d'un appel de fonction:

"""
----+
	| fonction
	| appelante
	|
	+---parametres-->
					|
					| fonction appelée
					|
	<---resultats---+
	|
"""
FORMATION PYTHON

Voici le schéma d'un appel d'une fonction décorée

"""
----+
	| fonction
	| appelante
	|
	+---parametres-->
					|
					| décorateur
					|
					+---parametres-->
					 				| fonction
					 				| appelée et décorée
					<---resultats---+
					|
	<---resultats---+
	|
----+
"""
FORMATION PYTHON

Décorateur sous forme de fonction

Ce décorateur va s'appuyer sur le mécanisme de la closure pour conserver les références sur la fonction décorée et les paramètres du décorateur, si il en a.

Sans paramètre

Le décorateur decorator va être appliqué à la fonction test via l'opérateur @
En écrivant:

def decorator(func):
	def inner(*args, **kwargs):
		return func(*args, **kwargs)
	return inner

On applique le décorateur à la fonction décorée

@decorotor
def test(a, b):

	return a * 2 + b

print(test(10, 2))

Cette écriture est équivalente à:

test = decorator(test)
print(test(10, 2))
FORMATION PYTHON

Avec paramètre(s)

Lorsqu'un décorateur possède des paramètres, ceux-ci au même titre que la fonction sont inclus dans une closure qui va englober la closure contenant la fonction. il y a donc 2 retours imbriqués de fonctions.

Par exemple:

def trace(out, upper=True):
	def decorator(func):
		def inner(*args, **kwars):
			# msg before call of func
			msg_in = f'in {func.__name__} with {args},{kwargs}'
			if upper:
				msg_in = msg_in.upper()
			out.write(msg_in)

			# call function
			ret = func(*args, **kwargs)

			# msg after called func
			msg_out = f'out {func.__name__} ret is {ret}'
			if upper:
				msg_out = msg_out.upper()
				out.write(msg_out)
		return inner
	return decorator
FORMATION PYTHON

Ce décorateur s'utilise comme suit:

import sys

@trace(sys.stderr, False)
def raven(*args):
	"""
	"""
	return sum(args)

print(raven(100, 200, 3, 9))

La mise en oeuvre du décorateur trace sur la fonction raven avec l'opérateur @ est équivalente à l'écriture suivante:

import sys

#  		 trace(out,		   upper) -> decorator
#						  decorator(func) -> inner
raven =  trace(sys.stderr, false)(raven)
FORMATION PYTHON

Décorateur sous forme de classe

Un décorateur peut aussi se définir sous la forme d'une classe. Dans ce cas, cette classe doit avoir au moins les 2 méthodes internes suivantes:

  • __init__ pour la partie déclaritive, cad la fonction à décorer
  • __call__ pour la partie appel de la fonction décorée, cad les paramètres de la fonction décorée

Voici un exemple de décorateur sous forme de classe:

class DecoSample
	def __init__(self, func):
		self._func = func

	def __call__(self, *args, **kwargs):
		# ne fait rien de spécial
		return self._func(*args, **kwargs)

@DecoSample
def add_plus_one(a, b):
	return a + b + 1
# équivalent à : add_plus_one = add_plus_one(func)

print(add_plus_one(3, 2))

Ici la fonction initiale add_plus_one est remplacée par une instance de DecoSample

FORMATION PYTHON

Avec paramètre(s)

Pour réaliser un décorateur avec paramètres sous forme de classe, une closure va être mise en place au niveau de la méthode __call__ . Ainsi cette méthode recevra elle même la fonction comme paramètre, et non plus les arguments comme auparavent.
Quand à la méthode __init__, elle recevra les paaramètes du décorateur

Pour réumé:

  • __init__ pour la partie déclaritive, cad les paramètres du décorateur
  • __call__ pour la partie déclarative, cad la fonction à décorer. Dans cette méthode, une closure sera mise en oeuvre pour renvoyer une fonction interne qui prendra en entrée les paramètres de la fonction décorée.
FORMATION PYTHON

Décorateur pour les classes

Il s'agit ici de décorer une classe, de pouvoir 'contrôler' les appels à ses méthodes. Dans ce cas de figure, cela revient à décorer des méthodes de cette classe sans positionner directement (Avec la syntaxe @decorator) le décorateur sur chaque méthode cible
Un exemple vous sera fourni

Contexte d'utilisation des décorateurs

Voici une liste non exhaustive de contextes d'utilisation:

  • trace d'appel de fonctions
  • compteur d'utilisation de fonctions
  • mesure de temps de réponse de fonctions
  • cache de résultats
  • autre .....
FORMATION PYTHON

Liste non exhaustive de décorateurs en Python

Voici quelques décorateurs internes:

  • @staticmethod
  • @classmethod
  • @property

Le module functools contient plusieurs décorateurs:

  • @wraps
  • @lru_cache

Le module contextlib contient en autre:

  • @contextmanager

Le module abc avec le décorateur

  • @abstractmethod