368 votes

Comprendre __get__ et __set__ et les descripteurs Python

Je suis en essayant pour comprendre ce que sont les descripteurs de Python et à quoi ils servent. Je comprends comment ils fonctionnent, mais voici mes doutes. Considérons le code suivant :

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()
  1. Pourquoi ai-je besoin de la classe des descripteurs ?

  2. Qu'est-ce que instance et owner ici ? (en __get__ ). Quel est le but de ces paramètres ?

  3. Comment pourrais-je appeler/utiliser cet exemple ?

168voto

li.davidm Points 4375

Le descripteur est le moyen par lequel le langage Python property est implémenté. Un descripteur implémente simplement __get__ , __set__ et est ensuite ajouté à une autre classe dans sa définition (comme vous l'avez fait ci-dessus avec la classe Température). Par exemple :

temp=Temperature()
temp.celsius #calls celsius.__get__

L'accès à la propriété à laquelle vous avez attribué le descripteur ( celsius dans l'exemple ci-dessus) appelle la méthode appropriée du descripteur.

instance sur __get__ est l'instance de la classe (donc ci-dessus, __get__ recevrait temp alors que owner est la classe avec le descripteur (donc ce serait Temperature ).

Vous devez utiliser une classe de descripteur pour encapsuler la logique qui l'alimente. De cette façon, si le descripteur est utilisé pour mettre en cache une opération coûteuse (par exemple), il pourra stocker la valeur sur lui-même et non sur sa classe.

Un article sur les descripteurs peut être trouvé ici .

EDIT : Comme jchl l'a souligné dans les commentaires, si vous essayez simplement de Temperature.celsius , instance sera None .

10 votes

Quelle est la différence entre self et instance ?

2 votes

Instance' peut être une instance de n'importe quelle classe, self sera une instance de la même classe.

6 votes

@LemmaPrism self est l'instance du descripteur, instance est l'instance de la classe (si elle est instanciée) dans laquelle se trouve le descripteur ( instance.__class__ is owner ).

138voto

andrew cooke Points 20902

Pourquoi ai-je besoin de la classe des descripteurs ?

Il vous donne un contrôle supplémentaire sur le fonctionnement des attributs. Si vous êtes habitué aux getters et setters en Java, par exemple, c'est la façon dont Python le fait. L'un des avantages est que, pour les utilisateurs, cela ressemble à un attribut (il n'y a pas de changement de syntaxe). Vous pouvez donc commencer par un attribut ordinaire et, lorsque vous avez besoin de faire quelque chose de plus sophistiqué, passer à un descripteur.

Un attribut est simplement une valeur mutable. Un descripteur vous permet d'exécuter du code arbitraire lors de la lecture ou de la définition (ou de la suppression) d'une valeur. On peut donc imaginer l'utiliser pour faire correspondre un attribut à un champ dans une base de données, par exemple - une sorte d'ORM.

Une autre utilisation pourrait consister à refuser d'accepter une nouvelle valeur en lançant une exception dans le champ __set__ - rendant effectivement l'"attribut" en lecture seule.

Qu'est-ce que instance et owner ici ? (en __get__ ). Quel est le but de ces paramètres ?

C'est assez subtil (et c'est la raison pour laquelle j'écris une nouvelle réponse ici - j'ai trouvé cette question en me demandant la même chose et je n'ai pas trouvé la réponse existante très bonne).

Un descripteur est défini sur une classe, mais il est généralement appelé depuis une instance. Lorsqu'il est appelé depuis une instance, les deux instance et owner sont fixés (et vous pouvez travailler owner de instance donc cela semble un peu inutile). Mais lorsqu'il est appelé depuis une classe, seul owner est défini - c'est pourquoi il est là.

Ceci n'est nécessaire que pour __get__ car c'est la seule qui peut être appelée sur une classe. Si vous définissez la valeur de la classe, vous définissez le descripteur lui-même. De même pour la suppression. C'est pourquoi le owner n'est pas nécessaire ici.

Comment pourrais-je appeler/utiliser cet exemple ?

Eh bien, voici une astuce sympa qui utilise des classes similaires :

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5

class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f

t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(J'utilise Python 3 ; pour Python 2 vous devez vous assurer que ces divisions sont / 5.0 et / 9.0 ). Cela donne :

100.0
32.0

Il existe d'autres moyens, sans doute meilleurs, d'obtenir le même effet en Python (par exemple, si la température était une propriété, ce qui est le même mécanisme de base mais place toute la source dans la classe Température), mais cela montre ce qui peut être fait...

2 votes

Les conversions sont fausses : elles devraient être C=5(F32)/9, F=32+9C/5.

1 votes

Assurez-vous que vous avez un objet de température. Faire ce qui suit met le bazar. t1 = Temperature(190) print t1.celsius t1.celsius = 100 print t1.fahrenheit Maintenant, quand vous vérifiez t.celcius et t.fahrenheit, ils sont modifiés aussi. t.celcius est 115 et t.fahrenheit est 32. ce qui est clairement faux. @Eric

1 votes

@IshanBhatt : Je pense que c'est à cause de l'erreur signalée par musiphil ci-dessus. Aussi, ce n'est pas ma réponse

7voto

gahcep Points 1789

Eh bien, je suis sûr à 100%, que le vidéo J'ai trouvé ( Découvrir les descripteurs d'EuroPython 2012) clarifiera tous les moments. Le conférencier a juste parfaitement donné tous les descripteurs internes. A voir absolument, en effet.

0voto

msoliman Points 732

-1voto

Gregory Kuhn Points 52

J'ai essayé (avec des changements mineurs comme suggéré) le code de la réponse d'Andrew Cooke. (J'utilise python 2.7).

Le code :

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

Le résultat :

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

Avec les versions antérieures à Python 3, assurez-vous de sous-classer l'objet, ce qui permettra au descripteur de fonctionner correctement comme l'objet obtenir La magie ne fonctionne pas pour les classes de l'ancien style.

1 votes

Les descripteurs ne fonctionnent qu'avec les nouvelles classes de style. Pour Python 2.x, cela signifie que vous devez dériver votre classe de "object", qui est par défaut dans Python 3.

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