Site original : Sam & Max: Python, Django, Git et du cul
Sur les décorateurs, normalement, vous avez tout ce qu’il faut pour être au point.
Néanmoins en informatique la moitié de la connaissance, c’est l’usage, pas la fonctionnalité. Car il y beaucoup d’usages auxquels on ne pense pas.
Particulièrement, je vous avais déjà expliqué que les fonctions étaient des objets comme les autres en Python, et qu’on pouvait donc les créer à la volée, les retourner, les passer en paramètre, et même leur coller des attributs.
Or les décorateurs ne sont jamais que des fonctions.
Maintenant, souvenez vous, le décorateurs property permet de faire ceci :
class Mamouth(object): _valeur = "3 calots" @property def valeur(self): return self._valeur.upper() @valeur.setter def valeur(self, valeur): self._valeur = valeur.strip() >>> bille = Mamouth() >>> bille.valeur u'3 CALOTS' >>> bille.valeur = "une pépite " >>> bille.valeur >>> print(bille.valeur) UNE PÉPITE
La syntaxe qui doit attirer votre attention est @valeur.setter
. En effet, d’où vient ce décorateur ?
On comprend mieux ce qui s’est passé avec ce test :
>>> Mamouth.valeur.setter <built-in method setter of property object at 0x1a1baf8>
setter
est tout simplement un attribut de la méthode valeur
. Par ailleurs, c’est une fonction et un décorateur.
Pourquoi faire cela ? Et bien tout simplement parce que cela permet d’attacher une fonction qui ne sert que dans un cas (ici ça ne sert qu’à créer le setter de la propriété valeur
) à son contexte.
Bien entendu vous pouvez faire ça vous même, il suffit de le vouloir très fort et de croire en le pouvoir de l’amour.
Par exemple, imaginez un décorateur qui permet d’attacher un comportement de sérialisation à une fonction. On ne veut pas modifier la fonction, mais on veut qu’elle puisse automatiquement, pour quelques caractères de plus, pouvoir aussi retourner du JSON ou du pickle.
import json import pickle def serializable(func): # Contrairement à la plupart des décorateurs, on ne va pas retourner # un wrapper, mais bien la fonction originale. Simplement on lui aura ajouté # des attributs func.as_json = lambda *a, **k: json.dumps(func(*a, **k)) func.as_pickle = lambda *a, **k: pickle.dumps(func(*a, **k)) return func
Et ça s’utilise ainsi :
import locale from calendar import TimeEncoding, day_name, day_abbr # obtenir les noms de jours localisés est complètement rocambolesque en python def get_day_name(day_number, locale, short=False): """ Retourne le nom d'un jour dans la locale sélectionnée. Exemple : >>> get_day_name(0, ('fr_FR', 'UTF-8')) 'lundi' """ with TimeEncoding(locale) as encoding: s = day_abbr[day_number] if short else day_name[day_number] return s.decode(encoding) if encoding is not None else s @serializable def get_days_names(locale=locale.getdefaultlocale(), short=False): """ Un dictionnaire contenant un mapping entre les numéros des jours de semaine et leurs noms selon la locale donnée. """ return {i: get_day_name(i, locale) for i in xrange(7)}
En usage ordinaire, la fonction retourne bien ce qui est prévu :
>>> get_days_names() {0: 'lundi', 1: 'mardi', 2: 'mercredi', 3: 'jeudi', 4: 'vendredi', 5: 'samedi', 6: 'dimanche'} >>> get_days_names(locale=('en_US', 'UTF-8')) {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday', 4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
Mais on peut choisir le format à la sortie :
>>> get_days_names.as_json() '{"0": "lundi", "1": "mardi", "2": "mercredi", "3": "jeudi", "4": "vendredi", "5": "samedi", "6": "dimanche"}' >>> get_days_names.as_pickle(locale=('en_US', 'UTF-8')) "(dp0\nI0\nS'Monday'\np1\nsI1\nS'Tuesday'\np2\nsI2\nS'Wednesday'\np3\nsI3\nS'Thursday'\np4\nsI4\nS'Friday'\np5\nsI5\nS'Saturday'\np6\nsI6\nS'Sunday'\np7\ns."
Ici, on a attacher une fonction à une autre fonction, en mettant la deuxième dans un attribut de la première.
Comme les décorateurs sont des fonctions, rien ne vous empêche de faire pareil avec un décorateur, et c’est de cette manière que @property
attache un décorateur setter
à chaque méthode.