web-dev-qa-db-fra.com

Comprendre les descripteurs __get__ et __set__ et Python

Je essayant de comprendre ce que sont les descripteurs Python et à quoi ils peuvent être utiles. Cependant, j'y échoue. 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 de descripteur?

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

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

282
Matt Bronson

Le descripteur décrit comment le type property de Python est implémenté. Un descripteur implémente simplement __get__, __set__, etc., puis est ajouté à une autre classe dans sa définition (comme vous l'avez fait ci-dessus avec la classe Temperature). Par exemple:

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

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

instance dans __get__ est l'instance de la classe (donc ci-dessus, __get__ recevrait temp, tandis que owner serait la classe avec le descripteur (donc être Temperature).

Vous devez utiliser une classe de descripteur pour encapsuler la logique qui l’alimente. Ainsi, si le descripteur est utilisé pour mettre en cache une opération coûteuse (par exemple), il peut 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 Temperature.celsius, instance sera None.

129
li.davidm

Pourquoi ai-je besoin de la classe de descripteur?

Cela 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 de faire de Python. Un des avantages est que les utilisateurs ont l’impression de ressembler à un attribut (la syntaxe ne change pas). Vous pouvez donc commencer avec un attribut ordinaire, puis basculer vers un descripteur lorsque vous devez faire quelque chose d'extraordinaire.

Un attribut est juste 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. Vous pouvez donc imaginer l'utiliser pour mapper un attribut sur un champ dans une base de données, par exemple, une sorte d'ORM.

Une autre utilisation pourrait être de refuser d'accepter une nouvelle valeur en lançant une exception dans __set__ - rendant ainsi "l'attribut" en lecture seule.

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

C'est assez subtil (et la raison pour laquelle j'écris une nouvelle réponse ici - j'ai trouvé cette question en me demandant la même chose et sans trouver la réponse existante aussi géniale).

Un descripteur est défini sur une classe, mais est généralement appelé à partir d'une instance. Lorsqu'il est appelé à partir d'une instance, instance et owner sont définis (et vous pouvez résoudre owner à partir de instance de sorte qu'il semble inutile). Mais lorsqu'il est appelé depuis une classe, seul owner est défini - c'est pourquoi il est présent.

Ceci n'est nécessaire que pour __get__ car c'est le seul qui puisse être appelé 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 la owner n'est pas nécessaire ici.

Comment pourrais-je appeler/utiliser cet exemple?

Eh bien, voici un truc sympa utilisant 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). Ça donne:

100.0
32.0

Il existe maintenant d’autres moyens, sans doute meilleurs, d’obtenir le même effet dans python (par exemple, si Celsius était une propriété, qui est le même mécanisme de base mais place toute la source dans la classe Temperature), mais cela montre ce que peut être fait...

98
andrew cooke

J'essaie de comprendre ce que sont les descripteurs de Python et à quoi ils peuvent être utiles.

Les descripteurs sont des attributs de classe (comme des propriétés ou des méthodes) comportant l'une des méthodes spéciales suivantes:

  • ___get___ (méthode sans descripteur, par exemple pour une méthode/fonction)
  • ___set___ (méthode du descripteur de données, par exemple sur une instance de propriété)
  • ___delete___ (méthode du descripteur de données)

Ces objets descripteurs peuvent être utilisés comme attributs dans d'autres définitions de classe d'objets. (C’est-à-dire qu’ils vivent dans le ___dict___ de l’objet de classe.)

Les objets descripteurs peuvent être utilisés pour gérer par programme les résultats d'une recherche en pointillés (par exemple _foo.descriptor_) dans une expression normale, une affectation et même une suppression.

Les fonctions/méthodes, les méthodes liées, property, classmethod et staticmethod utilisent toutes ces méthodes spéciales pour contrôler l'accès à celles-ci via la recherche en pointillé.

Un descripteur de données , comme property, peut permettre une évaluation paresseuse des attributs basée sur un état plus simple de l'objet, permettant ainsi aux instances d'utiliser moins mémoire que si vous avez précalculé chaque attribut possible.

Un autre descripteur de données, un _member_descriptor_, créé par __slots__ , permet d’économiser de la mémoire en permettant à la classe de stocker des données dans une structure de données mutable de type Tuple au lieu de plus flexible mais espace. -consuming ___dict___.

Les descripteurs autres que des données, généralement les méthodes d'instance, de classe et statiques, obtiennent leurs premiers arguments implicites (généralement nommés cls et self, respectivement) de leur méthode de descripteur non de données, ___get___.

La plupart des utilisateurs de Python n'ont besoin d'apprendre que l'utilisation simple et n'ont pas besoin d'apprendre ni de comprendre davantage la mise en oeuvre des descripteurs.

En profondeur: Que sont les descripteurs?

Un descripteur est un objet avec l'une des méthodes suivantes (___get___, ___set___ ou ___delete___), destiné à être utilisé via une recherche en pointillés comme s'il s'agissait d'un attribut typique d'une instance. . Pour un objet propriétaire, _obj_instance_, avec un objet descriptor:

  • _obj_instance.descriptor_ invoque
    descriptor.__get__(self, obj_instance, owner_class) retourne un value
    Voici comment fonctionnent toutes les méthodes et la get d'une propriété.

  • _obj_instance.descriptor = value_ invoque
    descriptor.__set__(self, obj_instance, value) retournant None
    Voici comment fonctionne la setter sur une propriété.

  • _del obj_instance.descriptor_ invoque
    descriptor.__delete__(self, obj_instance) retournant None
    Voici comment fonctionne la deleter sur une propriété.

_obj_instance_ est l'instance dont la classe contient l'instance de l'objet descripteur. self est l'instance du descripteur (probablement une seule pour la classe de la _obj_instance_)

Pour définir ceci avec du code, un objet est un descripteur si l'ensemble de ses attributs croise l'un des attributs requis:

_def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))
_

Un descripteur de données a un ___set___ et/ou ___delete___.
A Le descripteur non-data n'a ni ___set___ ni ___delete___.

_def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))
_

Exemples d'objets descripteurs intégrés:

  • classmethod
  • staticmethod
  • property
  • fonctions en général

Descripteurs non-données

Nous pouvons voir que classmethod et staticmethod sont des descripteurs non-data:

_>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
_

Tous deux ont uniquement la méthode ___get___:

_>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
_

Notez que toutes les fonctions sont aussi des descripteurs non-données:

_>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
_

Descripteur de données, property

Cependant, property est un descripteur de données:

_>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
_

Commande de recherche en pointillé

Celles-ci sont importantes distinctions , car elles affectent l'ordre de recherche pour une recherche en pointillé.

_obj_instance.attribute
_
  1. Tout d'abord, ce qui précède cherche à savoir si l'attribut est un descripteur de données sur la classe de l'instance,
  2. Sinon, il vérifie si l'attribut est dans le _obj_instance_'s ___dict___, puis
  3. il retombe finalement sur un descripteur non-données.

La conséquence de cet ordre de recherche est que les non-descripteurs de données tels que les fonctions/méthodes peuvent être remplacés par des instances .

Récapitulation et prochaines étapes

Nous avons appris que les descripteurs sont des objets avec l'un quelconque de ___get___, ___set___ ou ___delete___. Ces objets descripteurs peuvent être utilisés comme attributs dans d'autres définitions de classe d'objets. Nous allons maintenant voir comment ils sont utilisés, en utilisant votre code comme exemple.


Analyse du code de la question

Voici votre code, suivi de vos questions et réponses à chacune d’elles:

_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 de descripteur?

Votre descripteur garantit que vous avez toujours un flottant pour cet attribut de classe de Temperature et que vous ne pouvez pas utiliser del pour supprimer l'attribut:

_>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__
_

Sinon, vos descripteurs ignorent la classe de propriétaire et les instances de ce dernier, mais stockent l'état dans le descripteur. Vous pouvez tout aussi facilement partager l'état de toutes les instances avec un attribut de classe simple (tant que vous le définissez toujours comme un float de la classe et ne le supprimez jamais, ou que vous êtes à l'aise avec les utilisateurs de votre code):

_class Temperature(object):
    celsius = 0.0
_

Cela vous donne exactement le même comportement que votre exemple (voir la réponse à la question 3 ci-dessous), mais utilise une fonction intégrée Pythons (property) et serait considéré comme plus idiomatique:

_class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
_
  1. Quelle est l'instance et le propriétaire ici? (dans get ). Quel est le but de ces paramètres?

instance est l'instance du propriétaire qui appelle le descripteur. Le propriétaire est la classe dans laquelle l'objet descripteur est utilisé pour gérer l'accès au point de données. Voir les descriptions des méthodes spéciales qui définissent les descripteurs à côté du premier paragraphe de cette réponse pour plus de noms de variables descriptives.

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

Voici une démonstration:

_>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
_

Vous ne pouvez pas supprimer l'attribut:

_>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__
_

Et vous ne pouvez pas affecter une variable qui ne peut pas être convertie en float:

_>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
_

Sinon, vous avez ici un état global pour toutes les instances, géré en affectant une instance.

Le moyen attendu par les programmeurs Python les plus expérimentés d'atteindre ce résultat serait d'utiliser le décorateur property, qui utilise les mêmes descripteurs sous le capot, mais introduit le comportement dans la mise en œuvre du classe de propriétaire (encore une fois, comme défini ci-dessus):

_class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
_

Ce qui a exactement le même comportement attendu du morceau de code original:

_>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
_

Conclusion

Nous avons abordé les attributs qui définissent les descripteurs, la différence entre les descripteurs de données et les non-descripteurs de données, les objets intégrés qui les utilisent et des questions spécifiques relatives à leur utilisation.

Encore une fois, comment utiliseriez-vous l'exemple de la question? J'espère que tu ne le ferais pas. J'espère que vous commencerez par ma première suggestion (un attribut de classe simple) et passerez à la deuxième suggestion (le décorateur de la propriété) si vous le jugez nécessaire.

57
Aaron Hall

Avant d'entrer dans les détails des descripteurs, il peut être important de savoir comment fonctionne la recherche d'attribut dans Python. Cela suppose que la classe n'a pas de métaclasse et qu'elle utilise l'implémentation par défaut de __getattribute__ (les deux peuvent être utilisés pour "personnaliser" le comportement).

La meilleure illustration de la recherche d'attribut (dans Python 3.x ou dans le cas de classes de style nouveau dans Python 2.x) provient de Comprendre Python métaclasses (codelog d'ionel) . L'image utilise : comme substitut de "recherche d'attribut non personnalisable".

Ceci représente la recherche d'un attribut foobar sur un instance de Class:

enter image description here

Deux conditions sont importantes ici:

  • Si la classe de instance a une entrée pour le nom de l'attribut et qu'elle a __get__ et __set__.
  • Si la instance a aucune entrée pour le nom de l'attribut, mais la classe en a une et elle a __get__.

C'est là que les descripteurs entrent en jeu:

  • Descripteurs de données qui ont à la fois __get__ et __set__.
  • Des descripteurs sans données qui n'ont que __get__.

Dans les deux cas, la valeur renvoyée passe par __get__ appelé avec l'instance comme premier argument et la classe comme second argument.

La recherche est encore plus compliquée pour la recherche d'attribut de classe (voir par exemple Recherche d'attribut de classe (dans le blog mentionné ci-dessus) ).

Passons à vos questions spécifiques:

Pourquoi ai-je besoin de la classe de descripteur?

Dans la plupart des cas, vous n'avez pas besoin d'écrire des classes de descripteurs! Cependant, vous êtes probablement un utilisateur final très habituel. Par exemple des fonctions. Les fonctions sont des descripteurs, c'est ainsi que les fonctions peuvent être utilisées comme méthodes avec self implicitement passé en tant que premier argument.

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

Si vous recherchez test_method sur une instance, vous obtenez une "méthode liée":

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

De même, vous pouvez également lier une fonction en appelant sa méthode __get__ manuellement (ce qui n'est pas vraiment recommandé, à des fins d'illustration):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

Vous pouvez même appeler cette "méthode auto-liée":

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

Notez que je n'ai fourni aucun argument et que la fonction a renvoyé l'instance que j'avais liée!

Les fonctions sont Descripteurs non-données !

Quelques exemples intégrés d'un descripteur de données seraient property. Négliger getter, setter et deleter le descripteur property est (from Guide de description "Propriétés" ):

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

Comme c'est un descripteur de données, il est appelé à chaque fois que vous recherchez le "nom" du property et qu'il délègue simplement les fonctions décorées avec @property, @name.setter et @name.deleter ( si présent).

Il existe plusieurs autres descripteurs dans la bibliothèque standard, par exemple staticmethod, classmethod.

Le point des descripteurs est simple (bien que vous en ayez rarement besoin): Code commun abstrait pour l’accès aux attributs. property est une abstraction pour l'accès à une variable d'instance, function fournit une abstraction pour les méthodes, staticmethod fournit une abstraction pour les méthodes ne nécessitant pas d'accès à l'instance et classmethod fournit une abstraction pour les méthodes nécessitant un accès de classe plutôt qu'un accès d'instance (ceci est un peu simplifié).

Un autre exemple serait un propriété de classe .

Un exemple amusant (utilisant __set_name__ de Python 3.6) pourrait également être une propriété qui autorise uniquement un type spécifique:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

Ensuite, vous pouvez utiliser le descripteur dans une classe:

class Test(object):
    int_prop = TypedProperty(int)

Et en jouant un peu avec:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

Ou une "propriété paresseuse":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

Ce sont des cas où il serait logique de déplacer la logique dans un descripteur commun, mais on pourrait aussi les résoudre (mais peut-être en répétant du code) avec d'autres moyens.

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

Cela dépend de la façon dont vous recherchez l'attribut. Si vous recherchez l'attribut sur une instance, alors:

  • le deuxième argument est l'instance sur laquelle vous recherchez l'attribut
  • le troisième argument est la classe de l'instance

Si vous recherchez l'attribut sur la classe (en supposant que le descripteur est défini sur la classe):

  • le deuxième argument est None
  • le troisième argument est la classe où vous recherchez l'attribut

Donc, en gros, le troisième argument est nécessaire si vous souhaitez personnaliser le comportement lorsque vous effectuez une recherche au niveau de la classe (car la variable instance est None).

Comment pourrais-je appeler/utiliser cet exemple?

Votre exemple est essentiellement une propriété qui n'autorise que les valeurs pouvant être converties en float et qui est partagée entre toutes les instances de la classe (et sur la classe - bien que vous ne puissiez utiliser qu'un accès "en lecture" sur la classe, sinon remplacerait l'instance de descripteur):

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

C'est pourquoi les descripteurs utilisent généralement le deuxième argument (instance) pour stocker la valeur afin d'éviter de la partager. Cependant, dans certains cas, le partage d'une valeur entre instances peut être souhaité (bien que je ne puisse penser à un scénario pour le moment). Cependant, cela n’a pratiquement aucun sens pour une propriété Celsius dans une classe de température ... sauf peut-être comme un exercice purement académique.

10
MSeifert

Pourquoi ai-je besoin de la classe de descripteur?

Inspiré par Fluent Python par Buciano Ramalho

Imaging vous avez une classe comme ça

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("Apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the Apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

Nous devrions valider le poids et le prix en évitant de leur attribuer un nombre négatif, nous pouvons écrire moins de code si nous utilisons un descripteur comme proxy

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

puis définissez la classe LineItem comme ceci:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

et nous pouvons étendre la classe de quantité à faire la validation plus commune

6
wllbll

Vous verriez https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)
0
Yonks Somarl

J'ai essayé (avec des modifications mineures 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 __== "__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 Python avant 3, assurez-vous de sous-classe à partir de l'objet qui fera en sorte que le descripteur fonctionne correctement, car la magie get ne fonctionne pas pour les anciennes classes de style.

0
Gregory Kuhn