-2

Je souhaite créer un décorateur de propriété qui calcule la valeur de la propriété une fois lors de l'initialisation de l'objet, plutôt qu'à chaque fois que la propriété est accédée. Par exemple:Est-il possible de patcher le code sur __init__ depuis un décorateur de méthode?

class Foo: 

    def __init__(self, value): 
    self.value = value 

    @cached_property # How to implement this decorator? 
    def foo(self): 
    return self.value * some_heavy_computation() 

Je voudrais que cela soit équivalent à:

class Foo: 

    def __init__(self, value): 
    self.value = value 
    self._foo = self.value * some_heavy_computation() 

    @property 
    def foo(self): 
    return self._foo 

Est-il possible d'ajouter en quelque sorte le code à __init__() à l'intérieur du décorateur méthode?

+2

Au moment où un décorateur de méthode est évaluée, la classe elle-même ne existe encore, donc vous ne pouvez pas rechercher sa méthode '' __init__'' pour la modifier, même si elle a déjà été définie (ce qui n'est pas quelque chose sur lequel vous pouvez compter). Cependant, vous pouvez écrire un décorateur qui n'appelle la fonction enveloppée que la première fois, enregistre la valeur dans une variable d'instance et renvoie ensuite simplement la valeur mise en cache. – jasonharper

+1

Pourquoi pensez-vous que vous devez ajouter du code à '__init__'? Vous devriez lire le [protocole descripteur] (https://docs.python.org/3/howto/descriptor.html). – jonrsharpe

+0

@ jasonharper Impossible d'obtenir '__init__' du même' cls' et de le remplacer par un '__init__' patché? Disons que je suppose que '__init__' est toujours défini avant les autres méthodes. – danijar

Répondre

0

Nous avons besoin de sous classe property afin que nous puissions trouver plus tard toutes les propriétés mises en cache de l'objet et les initialiser après __init__:

class CachedProperty(property): 

    pass 

Le décorateur réelle évalue le corps de la méthode lorsqu'il est appelé pour la première fois, et rappelle le résultat pour les accès ultérieurs:

import functools 

def cached_property(method): 
    attribute = '_cached_' + method.__name__ 

    @CachedProperty 
    @functools.wraps(method) 
    def wrapper(self, *args, **kwargs): 
    if not hasattr(self, attribute): 
     setattr(self, attribute, method(self)) 
    return getattr(self, attribute) 

    return wrapper 

maintenant, nous pouvons utiliser une classe de base pour accéder aux propriétés mises en cache après __init__ afin que les valeurs seront disponibles à partir du cache:

class InitCachedProperties: 

    def __init_subclass__(cls, **kwargs): 
    super().__init_subclass__(**kwargs) 
    orig_init = cls.__init__ 
    def init(self, *args, **kwargs): 
     orig_init(self, *args, **kwargs) 
     for prop in cls.__dict__.values(): 
     if isinstance(prop, CachedProperty): 
      prop.__get__(self) 
    cls.__init__ = init 

Pour rendre l'exemple du travail de question, nous devons laisser notre Hériter de classe de cette propriété d'initialisation classe de base:

class Foo(InitCachedProperties): 

    def __init__(self, value): 
     self.value = value 

    @cached_property 
    def foo(self): 
     return self.value + 21