Le décorateur @property en Python

A quoi sert ce décorateur en python ?

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Salut les clem's,

J'ai découvert les décorateurs en Python il y a pas longtemps et je les trouve fascinant mais par hasard je suis tombée sur ce décorateur @property, j'ai fait mes petites recherches, j'ai juste compris qu'il faisait références aux mutateurs et acesseurs de python.

Donc ma question serait : Comment on s'en sert ? Et pourquoi ?

Merci d'avance :)

+0 -0

Tu as plein de réponses en anglais ou en français en cherchant "property python" sur google. Il faut d'abord que tu lises comment les properties marchent, ensuite tu verras que la syntaxe avec les décorateurs permet de les écrire rapidement.

+0 -0
Auteur du sujet

Si j'ai bien compris, ça remplace juste la fonction property en décorateur ? Sinon dernière question et après je vous laisse : Comment pourrait-on se servir d'un décorateur dans un (gros) projet ?

Merci encore :)

+0 -0
Staff

Tout dépend de ce que fait ton décorateur…

C'est principalement un sucre syntaxique, donc ça dépend vraiment complètement des cas d'utilisation.

I was a llama before it was cool

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

Ben à titre d'exemple j'en utilise à deux endroits sur le projet sur lequel je bosse en ce moment (un compilateur).

Autre cas beaucoup plus simple d'utilisation. Dans mon lexeur, j'ai :

  • une liste tokens qui contient les noms de mes classes de lexèmes.
  • pour chaque token, une fonction sous la forme :
1
2
3
4
def t_NOM_DU_TOKEN(t):
    r'expression régulière associée'
    # code de la fonction
    return t  

Pour être sûr de ne jamais oublier de modifier la liste chaque fois que j'ajoute une telle fonction, j'ai écrit un petit décorateur :

1
2
3
4
def addtoken(fn):
    '''Decorator to define a token through the corresponding t_* function'''
    tokens.append(fn.__name__.replace('t_', ''))
    return fn

Ce qui me permet de déclarer mes tokens et mes fonctions dans la même foulée :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@addtoken
def t_INT_TYPE(t):
    r'(u|s)8|(u|s)(16|32|64)(h|n)?'
    return t

@addtoken
def t_STR_CONST(t):
    r'".*?[^\\]"'
    t.value = t.value[1:-1]
    return t

Ce code va automatiquement rajouter 'INT_TYPE' et 'STR_CONST' dans ma liste tokens.

En gros, les décorateurs permettent de gagner du temps, mais il faut un peu d'imagination et pas mal de recul sur ton code pour trouver des cas d'utilisation pertinents.

Édité par nohar

I was a llama before it was cool

+2 -0

Cette réponse a aidé l'auteur du sujet

Un autre exemple, quand je veux avoir des infos sur des fonctions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def getInfos(f):
    def wrap(*args):
        args = tuple(map(str, args))
        res = f(*args)
        print("fonction utilisée : {}".format(f.__name__))
        print("arguments : {}".format(' '.join(args)))
        print("type de retour : {}".format(type(f(*args))))
        print("result : {}".format(res))
    return wrap

@getInfos
def test_1(a, b, c):
    return a, b, c

@getInfos
def test_2(a, b, c):
    return a

test_1(1, 2, 3)

test_2("a", "b", "c")

Je l'utilise aussi pour mesurer les temps d'exécution de chaque fonction, etc…

+1 -0

Cette réponse a aidé l'auteur du sujet

Un autre exemple pertinent je trouve est l'utilisation qui en est faite avec des frameworks tels que Bottle. Ils permettent en effet de définir aisément le routage des URLs en associant un motif à une vue.

1
2
3
4
5
6
7
@route('/hello/<name>')
def profile(name):
    return template('<b>Hello {{name}}</b>!', name=name)

@route('/')
def home():
    return 'Homepage'

@fred: attention, ton wrapper ne retourne pas le résultat de la fonction décorée.

Oui car j'utilise l'affichage du résultat avec print… Je ne l'avais pas mis pour éviter la redite du résultat ;)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def getInfos(f):
    def wrap(*args):
        args = tuple(map(str, args))
        res = f(*args)
        print("fonction utilisée : {}".format(f.__name__))
        print("arguments : {}".format(' '.join(args)))
        print("type de retour : {}".format(type(res)))
        print("result : {}".format(res))
        return res
    return wrap

@getInfos
def test_1(a, b, c):
    return a, b, c

@getInfos
def test_2(a, b, c):
    return a

print(test_1(1, 2, 3))

print(test_2("a", "b", "c"))

Édité par fred1599

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

D'ailleurs pendant que j'y pense, vous vous servez souvent des metaclass ?

Cirdo

Non.

Ça a dû m'arriver une fois en 5 ans, pour une bibliothèque vraiment particulière que je n'ai toujours pas terminée.

I was a llama before it was cool

+1 -0

Cette réponse a aidé l'auteur du sujet

D'ailleurs pendant que j'y pense, vous vous servez souvent des metaclass ?

Pour essayer, mais j'en ai jamais eu l'intérêt dans un problème posé…

Comme le dirait un grand de python (Tim Peters)

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).

Il me semble avoir déjà lu que les métaclasses sont souvent utilisées dans la construction d'API

Édité par fred1599

+1 -0

Cette réponse a aidé l'auteur du sujet

Moi m'sieur, moi m'sieur ! J'ai déjà vu des métaclasses utilisées brillamment dans un projet auquel j'ai participé.

En gros, on avait un certain nombre d'objets très variés qui devaient être enregistrés automatiquement lors de l'exécution d'un programme. Problème : le programme en question était en développement, et les enregistrements ne devaient pas être brisés en cas de modification d'une classe d'un de ces objets. Pour ça, mon chef de projet a mis en place un système de versionnage assez complexe ; les classes instanciant des objets à enregistrer héritent d'une classe mère, appelons-la OBase (elle contient notamment les méthodes d'enregistrement, etc.). OBase a pour métaclasse MetaOBase, et possède un attribut de classe _version ; à l'exécution du programme, au moment de reconstruire chaque objet désérialisé, Python passe par cette métaclasse et si la version a changé, elle se charge de mettre à jour l'objet en conséquence, en récupérant la classe de versionnage nécessaire, et en faisant plein d'autres choses compliquées dont je ne me rappelle plus en détail.

Franchement, c'était magnifique, et en plus ça marchait ! :p

ZdS : un palimpzeste pour le savoir. | Les codes correcteurs, c'est bien.

+1 -0

Haha ce mec était un fou. Il a porté ce projet à bout de bras, et même si je dois avoir à mon actif une dizaine de milliers de lignes de codes pour lui, j'ai l'impression de n'avoir rien fait. Il a construit from scratch un moteur titanesque, une vraie usine à gaz, pour que les gens qui bossaient avec lui n'aient à coder que les plus hautes couches d'abstraction… Avec le recul, je pense qu'il avait des journées de 36h, c'pas possible autrement. ^^

ZdS : un palimpzeste pour le savoir. | Les codes correcteurs, c'est bien.

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

En fait, pour répondre de façon plus générale à la question des métaclasses, celles-ci deviennent utiles à partir du moment où tu veux modifier une classe juste avant qu'elle ne soit définie.

Niveau fonctionnalité, ça peut ressembler à l'application d'un décorateur sur une classe. En fait, c'est sensiblement différent parce qu'un décorateur intervient juste après que l'objet qu'il décore ne soit défini. De plus, la métaclasse est héritée par les classes filles, à la différence d'un décorateur de classe qui ne s'applique qu'à son argument.

Prenons un exemple qui va nous permettre de voir comment se débrouille Python.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyMetaClass(type):
    def __new__(cls, clsname, bases, dct):
        print("MyMetaClass", clsname, dct)
        # Ici on peut modifier le nom que portera la classe
        # Ou bien lui rajouter ou lui supprimer des classes mères
        # Ou bien modifier ses attributs de classe ou ses méthodes
        return super().__new__(cls, clsname, bases, dct)

def decorator(obj):
    print(obj, "was just defined")
    return obj

@decorator
class MyClass(metaclass=MyMetaClass):
    @decorator
    def __new__(cls, *args):
        print("MyClass.__new__", *args)
        return super().__new__(cls)

    @decorator
    def __init__(self, bar):
        print("MyClass.__init__", bar)
        self.bar = bar

a = MyClass("foo")

À l'exécution, ça donne :

1
2
3
4
5
6
<function MyClass.__new__ at 0x7f35df171bf8> was just defined
<function MyClass.__init__ at 0x7f35df171c80> was just defined
MyMetaClass MyClass {'__qualname__': 'MyClass', '__module__': '__main__', '__init__': <function MyClass.__init__ at 0x7f35df171c80>, '__new__': <function MyClass.__new__ at 0x7f35df171bf8>}
<class '__main__.MyClass'> was just defined
MyClass.__new__ foo
MyClass.__init__ foo

Dans l'ordre :

  • Python a compilé et défini la méthode __new__ de la classe MyClass. Cette méthode est la toute première que Python appelle pour instancier un objet.
  • Python a compilé et défini la méthode __init__ de la classe MyClass.
  • C'est là que c'est intéressant. Il n'a même pas encore fini de définir la classe MyClass qu'il instancie MyMetaClass avec trois arguments :
    • le nom de la classe MyClass dont MyMetaClass est la méta-classe.
    • le tuple des classes dont hérite MyClass (en dehors de object). Ici il n'y en a aucune.
    • le dictionnaire des attributs de MyClass

Cet appel à __new__ est chargé de construire la classe. En gros, puisque tout est objet en Python, ça revient à instancier la classe type. Cela veut dire que dans cette méthode, on peut modifier la classe elle-même avant qu'elle ne soit créée (ou même après, du moment que l'on retourne la classe à la fin). Par exemple on peut lui rajouter des méthodes ou des attributs dynamiquement.

  • Une fois que MyMetaClass.__new__() retourne, l'objet résultant (qui est une classe), est enregistrée dans le scope courant sous le nom MyClass.

On en arrive à la dernière ligne du script : qui illustre simplement le fait que __new__ soit le constructeur à proprement parler de la classe, puisqu'il est appelé avant __init__.

Édité par nohar

I was a llama before it was cool

+0 -0
Auteur du sujet

Haha ce mec était un fou. Il a porté ce projet à bout de bras, et même si je dois avoir à mon actif une dizaine de milliers de lignes de codes pour lui, j'ai l'impression de n'avoir rien fait. Il a construit from scratch un moteur titanesque, une vraie usine à gaz, pour que les gens qui bossaient avec lui n'aient à coder que les plus hautes couches d'abstraction… Avec le recul, je pense qu'il avait des journées de 36h, c'pas possible autrement. ^^

Alkareth

Ce mec est-t-il un dieu en python ? Je pense que oui.

Nohar : Ta réponse est très complète et très fournie, je garde cette page quand j'appendrai les meta-class en python, je l'encadrerai sur le mur de ma chambre ;)

Édité par Cirdo

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte