1320 votes

Comment fonctionne le décorateur @property en Python?

Je voudrais comprendre comment fonctionne la fonction intégrée property. Ce qui me perturbe, c'est que property peut également être utilisé comme un décorateur, mais il ne prend des arguments que lorsqu'il est utilisé comme une fonction intégré et non lorsqu'il est utilisé comme un décorateur.

Cet exemple provient de la documentation:

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "Je suis la propriété 'x'.")

Les arguments de property sont getx, setx, delx et une chaîne de documentation.

Dans le code ci-dessous, property est utilisé comme un décorateur. L'objet de celui-ci est la fonction x, mais dans le code ci-dessus, il n'y a pas de place pour une fonction objet dans les arguments.

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Je suis la propriété 'x'."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Comment sont créés les décorateurs x.setter et x.deleter dans ce cas ?

28 votes

6 votes

property est en fait une classe (pas une fonction), bien qu'elle appelle probablement la méthode __init__() lorsque vous créez un objet, bien sûr. Utiliser help(property) depuis le terminal est instructif. help est également une classe pour une raison quelconque.

4 votes

Je pense que ce lien offre un bon exemple : [propriété] (journaldev.com/14893/python-property-decorator)

10voto

prosti Points 4630

property est une classe derrière le décorateur @property.

Vous pouvez toujours vérifier cela :

print(property) #

J'ai réécrit l'exemple de help(property) pour montrer que la syntaxe @property

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

est fonctionnellement identique à la syntaxe property() :

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

Il n'y a aucune différence dans la façon dont nous utilisons la propriété comme vous pouvez le voir.

Pour répondre à la question, le décorateur @property est implémenté via la classe property.


Donc, la question est d'expliquer un peu la classe property. Cette ligne :

prop = property(g,s,d)

était l'initialisation. Nous pouvons la réécrire comme ceci :

prop = property(fget=g,fset=s,fdel=d)

La signification de fget, fset et fdel :

 |    fget
 |      fonction à utiliser pour obtenir une valeur d'attribut
 |    fset
 |      fonction à utiliser pour définir une valeur d'attribut
 |    fdel
 |      fonction à utiliser pour supprimer un attribut
 |    doc
 |      docstring

L'image suivante montre les triplets que nous avons, de la classe property :

entrez la description de l'image ici

__get__, __set__, et __delete__ sont là pour être surchargés. C'est l'implémentation du modèle de descripteur en Python.

En général, un descripteur est un attribut d'objet avec un "comportement de liaison", dont l'accès à l'attribut a été remplacé par des méthodes dans le protocole de descripteur.

Nous pouvons également utiliser les méthodes setter, getter et deleter de la propriété pour lier la fonction à la propriété. Vérifiez l'exemple suivant. La méthode s2 de la classe C définira la propriété doubled.

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x

    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      

c = C()
c.x="a"
print(c.x) # renvoie "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # renvoie "aa"

5voto

Yilmaz Points 51

Un décorateur est une fonction qui prend une fonction en argument et renvoie une fermeture. La fermeture est un ensemble de fonctions internes et de variables libres. La fonction interne referme la variable libre et c'est pourquoi on l'appelle 'fermeture'. Une variable libre est une variable qui est en dehors de la fonction interne et passée à l'intérieur via le décorateur.

Comme son nom l'indique, le décorateur décore la fonction reçue.

function decorator(undecorated_func):
    print("appel de la fonction décoratrice")
    inner():
       print("Je suis à l'intérieur de la fonction interne")
       return undecorated_func
    return inner

Il s'agit d'une simple fonction décoratrice. Elle a reçu "undecorated_func" et l'a transmise à inner() en tant que variable libre, inner() a affiché "Je suis à l'intérieur de la fonction interne" et a renvoyé undecorated_func. Lorsque nous appelons decorator(undecorated_func), cela renvoie la inner. Voici l'astuce, dans les décorateurs nous nommons la fonction interne comme le nom de la fonction que nous avons passée.

   undecorated_function= decorator(undecorated_func) 

Maintenant, la fonction interne s'appelle "undecorated_func". Puisque inner est maintenant nommé "undecorated_func", nous avons passé "undecorated_func" au décorateur et nous avons renvoyé "undecorated_func" plus affiché "Je suis à l'intérieur de la fonction interne". donc cette instruction d'impression a décoré notre "undecorated_func".

Maintenant définissons une classe avec un décorateur de propriété :

class Person:
    def __init__(self,name):
        self._name=name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self.value):
        self._name=value

Lorsque nous avons décoré name() avec @property(), voici ce qui s'est passé :

name=property(name) # Person.__dict__ vous verrez le nom

Le premier argument de property() est le getter. C'est ce qui s'est passé dans le deuxième décor :

   name=name.setter(name) 

Comme je l'ai mentionné ci-dessus, le décorateur renvoie la fonction interne, et nous nommons la fonction interne avec le nom de la fonction que nous avons passée.

Voici une chose importante à noter. "nom" est immuable. Dans le premier décor, nous avons obtenu ceci :

  name=property(name)

dans la seconde nous avons obtenu ceci

  name=name.setter(name)

Nous ne modifions pas l'objet name. Dans le second décor, Python voit que c'est un objet de propriété et qu'il avait déjà un getter. Python crée donc un nouvel objet "nom", ajoute le "fget" du premier objet, puis définit le "fset".

0 votes

Votre réponse contient beaucoup de fautes de frappe et d'erreurs de syntaxe qui m'ont empêché de la lire.

0 votes

@thanos.a Je suis tellement désolé à ce sujet :) J'ai corrigé quelques fautes de frappe mais je ne vois aucune erreur de syntaxe

2voto

nvd Points 379

Une propriété peut être déclarée de deux façons.

  • Création des méthodes getter, setter pour un attribut, puis passez celles-ci en argument à la fonction property
  • Utilisation du décorateur @property.

Vous pouvez consulter quelques exemples que j'ai écrits sur les propriétés en python.

0 votes

Pouvez-vous mettre à jour votre réponse en disant que la propriété est une classe afin que je puisse voter favorablement ?

0voto

Acecool Points 26

Voici un autre exemple :

##
## Exemple de propriétés en Python
##
classe GetterSetterExample( object ):
    ## Définir la valeur par défaut pour x (nous y référençons en utilisant self.x, définissons une valeur en utilisant self.x = valeur)
    __x = None

##
## À l'initialisation de la classe - faire quelque chose... si nous le souhaitons..
##
def __init__( self ):
    ## Définir une valeur pour __x à travers le getter / setter... Puisque __x est défini ci-dessus, cela n'a pas besoin d'être défini...
    self.x = 1234

    return None

##
## Définir x comme une propriété, c'est-à-dire un getter - Tous les getters devraient avoir un argument de valeur par défaut, donc je l'ai ajouté - il ne sera pas passé lors du paramétrage d'une valeur, vous devez donc définir la valeur par défaut ici pour qu'elle soit utilisée..
##
@property
def x( self, _default = None ):
    ## J'ai ajouté un argument de valeur par défaut facultatif car tous les getters devraient avoir ceci - définissez-le sur la valeur par défaut que vous voulez retourner...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Débogage - pour que vous puissiez voir l'ordre des appels...
    print('[ Test Class ] Get x = ' + str( _value ) )

    ## Retourner la valeur - nous sommes un getter après tout...
    return _value

##
## Définir la fonction setter pour x...
##
@x.setter
def x( self, _value = None ):
    ## Débogage - pour que vous puissiez voir l'ordre des appels...
    print('[ Test Class ] Set x = ' + str( _value ) )

    ## Cela montre que la fonction setter fonctionne.... Si la valeur est supérieure à 0, définissez-la sur une valeur négative... sinon gardez-la telle quelle (0 est la seule valeur non-négative, elle ne peut être ni négative ni positive de toute façon)
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value

##
## Définir la fonction de suppression pour x...
##
@x.deleter
def x( self ):
    ## Libérer l'affectation / les données pour x
    if ( self.__x != None ):
        del self.__x

##
## Fonction de chaîne / sortie pour la classe - cela montrera la valeur de propriété pour chaque propriété que nous ajoutons...
##
def __str__( self ):
    ## Afficher les données de la propriété x...
    print('[ x ] ' + str( self.x ) )

    ## Retourner une nouvelle ligne - techniquement nous devrions retourner une chaîne afin qu'elle puisse être imprimée où nous le voulons, au lieu d'être imprimée tôt si _data = str( C( ) ) est utilisé....
    return '\n'

##
##
##
_test = GetterSetterExample()
print(_test)

## Pour une raison quelconque, la fonction de suppression n'est pas appelée...
del _test.x

Essentiellement, c'est la même chose que l'exemple C( object ) sauf que j'utilise x à la place... Je n'initialise pas non plus dans __init - ... bon... en fait, si, mais ça peut être enlevé parce que __x est défini comme partie de la classe...

La sortie est :

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

et si je commente self.x = 1234 dans init alors la sortie est :

[ Test Class ] Get x = None
[ x ] None

et si je définis _default = None en _default = 0 dans la fonction getter (car tous les getters devraient avoir une valeur par défaut mais elle n'est pas passée par les valeurs de propriété d'après ce que j'ai vu, donc vous pouvez la définir ici, et en fait ce n'est pas mal car vous pouvez définir la valeur par défaut une fois et l'utiliser partout) c'est-à-dire : def x( self, _default = 0) :

[ Test Class ] Get x = 0
[ x ] 0

Remarque : La logique du getter est là juste pour que la valeur soit manipulée par lui pour s'assurer qu'elle est manipulée par lui - de même pour les instructions d'impression...

Remarque : Je suis habitué à Lua et à être capable de créer dynamiquement plus de 10 assistants quand j'appelle une seule fonction et j'ai fait quelque chose de similaire pour Python sans utiliser de propriétés et cela fonctionne dans une certaine mesure, mais, même si les fonctions sont créées avant d'être utilisées, il y a encore parfois des problèmes avec leur appel avant d'être créées ce qui est étrange car ce n'est pas codé de cette façon... Je préfère la flexibilité des métatables Lua et le fait que je peux utiliser de véritables setters / getters au lieu d'accéder directement à une variable... J'aime cependant la rapidité avec laquelle certaines choses peuvent être construites avec Python - par exemple les programmes d'interface graphique. bien que celui que je conçois puisse ne pas être possible sans beaucoup de bibliothèques supplémentaires - si je le code en AutoHotkey je peux accéder directement aux appels dll dont j'ai besoin, et la même chose peut être faite en Java, C#, C++, et plus - peut-être que je n'ai pas encore trouvé la bonne chose mais pour ce projet je pourrais passer de Python...

Remarque : La sortie de code sur ce forum est cassée - j'ai dû ajouter des espaces au début du code pour le faire fonctionner - lorsque vous copiez / collez assurez-vous de convertir tous les espaces en tabulations.... J'utilise des tabulations pour Python car dans un fichier de 10 000 lignes la taille du fichier peut être de 512 Ko à 1 Mo avec des espaces et de 100 à 200 Ko avec des tabulations ce qui équivaut à une différence massive de taille de fichier et à une réduction du temps de traitement...

Les tabulations peuvent également être ajustées par utilisateur - donc si vous préférez une largeur de 2 espaces, 4, 8 ou autre, vous pouvez le faire ce qui est réfléchi pour les développeurs avec des déficiences visuelles.

Remarque : Toutes les fonctions définies dans la classe ne sont pas indentées correctement à cause d'un bug dans le logiciel du forum - assurez-vous de l'indenter si vous copiez / collez

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X