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.
Télécharger le code des articles
pourquoi :
et pas ?
L’interêt d’un décorateur, c’est qu’on peut l’ajouter et le retirer à loisir, et sur plusieurs fonctions.
Si on utilise juste
**k
, on ne peut plus utiliser le decorateur sur des fonctions avec des paramètres positionnels :ok merci
Salut Sam & Max ! J’ai découvert votre blog en début d’année, et ma connaissance de Python est un gros gruyère. J’estime qu’il y a plein de choses subtiles que je ne connais pas du langage, et c’est souvent que j’en découvre grâce à votre blog en me disant à chaque fois : p’tain, c’est quand même de la balle, Python. Aujourd’hui ne fait pas exception, alors j’en profite pour une fois pour vous dire un grand merci !
Mais j’ai une question : décorateur différent ou pas, j’étais persuadé qu’il n’était pas possible de créer 2 méthodes du même nom, même si le nombre d’arguments différait. Comment se fait-ce ?
En fait, on ne créer pas deux méthodes de même nom, car en Python on ne donne pas de nom aux méthodes. Sous le capot,
def
attache simplement la référence à la méthode à un nom. Quand la seconde méthode est crée, elle est attachée à ce nom et l’ancienne n’est plus attachée à ce nom, mais ici ne disparaît pas pour autant.En effet, un objet en Python n’est supprimé que dans le cas où aucune référence ne pointe vers lui. Dans notre cas précis,
@property
garde une référence interne de la méthode et elle n’est pas supprimée.Pour aller plus loin dans l’explication :
@property
va créer un descripteur qu’il va placer en lieu et place de l’attribut portant le nom des méthodes. Il va ensuite attacher chaque méthode à ce descripteur. Au final, quand on lit, écrit ou supprime le contenu de l’attribut, on appelle le descripteur, qui lui appelle les méthodes.Le descripteur a donc une référence pour chaque méthode décorée, ainsi les méthodes ne sont jamais supprimées, et reste accessibles.
Mangez des pommes !
Ok, c’est la non-disparition que je n’avais pas comprise. Merci !
Ce que je ne comprends pas (mais que j’accepte), c’est pourquoi, alors que le décorateur
@valeur.setter
mentionne le nom de la property (valeur
), la méthode doit être nomméevaleur
aussi. Si je change le nom :Python me dit qu’il ne trouve pas la méthode
valeur_set
quand je faisbille.valeur = "foo"
. Ca paraît un peu redondant. Ca paraîtrait, dans ce cas, plus logique d’avoir un décorateur de nom@property.setter
, si c’est pour devoir, de toute façon, nommer la méthode intelligemment.Je me suis fait la même remarque, il faudrait regarder dans le code source pour trouver la réponse, mais j’ai la flemme.