-1
votes

Préserver la signature de fonction exacte dans la chaîne de décorateurs à l'aide de FuncTools.wraps

in python 3.4+, functools.arras code> conserve la signature de la fonction qu'elle enveloppe. Malheureusement, si vous créez des décorateurs destinés à être empilés les uns sur les autres, le second (ou la dernière) décorateur de la séquence constatera le générique * args code> et ** kwargs Signature du wrapper em> et ne préservant pas la signature de la fonction d'origine jusqu'au bas de la séquence des décorateurs. Voici un exemple. XXX PRE>

Ceci donne P>

-------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-5-69c17467332d> in <module>
     22
     23
---> 24 foo()

<ipython-input-5-69c17467332d> in wrapper(*args, **kwargs)
      4     @wraps(func)
      5     def wrapper(*args, **kwargs):
----> 6         assert kwargs['x'] <= 2
      7         return func(*args, **kwargs)
      8     return wrapper

KeyError: 'x'


0 commentaires

3 Réponses :


2
votes

Vous n'êtes pas en train de transmettre des arguments à votre fonction foo donc * args et ** kwargs sont vides pour les deux décorateurs. Si vous passez des arguments, les décorateurs fonctionnent juste bien xxx

Vous pouvez essayer d'obtenir des arguments de fonction par défaut à l'aide de Inspectez


2 commentaires

Je ne sais pas qui a évité cela, mais il est totalement correct et totalement pertinent - même s'il ne suffit pas de résoudre le problème.


Je ne peux pas parler à tout cela, mais comme la personne qui pose la question, je ne vois pas cela aussi utile car la question est très spécifique au cas de ne pas réussir Kwargs à la fonction décorée, et les avoir à la fois référencés À partir des paramètres par défaut de la fonction décorée sans Utilisation de Inspect (par exemple, s'appuyer sur les enveloppements fait de sorte que dans le corps de l'emballage, vous pouvez accéder aux valeurs par défaut d'origine) .



1
votes

Vous ne pouvez pas vraiment obtenir les valeurs par défaut sans utiliser inspecter et vous devez également compter pour positionner les arguments de position ( * args ) vs mots-clés args ( * * kwargs ). Donc, normaliser les données si c'est là s'il est manquant, inspectez la fonction xxx

ceci imprime xxx


4 commentaires

Notez que le décorateur intérieur, tel que @validate_y fonctionne simplement dans mon exemple. Il utilise kwargs à partir de la fonction emballée sans aucun inspecter manipulation. L'erreur ne se produit que pour le décorateur externe que wraps (...) une autre fonction décorée.


Lorsque j'ai dirigé votre code dans le débogueur, les arguments et les kwargs étaient vides. En effet, les valeurs par défaut sont remplies au moment où la fonction est appelée non auparavant. C'est pourquoi j'ai ajouté la normalisation de args -> kwargs et nécessaire inspecter pour obtenir les valeurs par défaut.


Cela est vrai pour le décorateur extérieur, mais ne semble pas être vrai pour le décorateur intérieur.


oely le décorateur d'intérieur @validate_y ne fonctionne pas non plus dans votre exemple; C'est juste que vous n'avez pas donné une chance d'échouer, car @validate_x a d'abord échoué. Le comportement est le même si vous utilisez un ou les deux décorateurs; De toute façon, il soulève un KeyError .



0
votes

Votre diagnostic est incorrect; En fait, functools.wraps code> conserve la signature de la fonction à double décoration:

def validate_x(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if 'x' in kwargs and kwargs['x'] > 2:
            raise ValueError('Invalid x, should be <= 2, was ' + str(x))
        return func(*args, **kwargs)
    return wrapper

@validate_x
def bar(*, x=1): # keyword-only arg, prevent passing as positional arg
    ...


7 commentaires

J'ai déjà abordé cela dans ma question. Notez que inspect.signature a un paramètre suivi_wraped VRAI par défaut, mais la signature réelle utilisée dans la fonction Call Machinery est différente, ne suivant pas les wraps.


La signature réelle du wrapper est (* args, ** kwargs) car c'est comme ça que vous l'avez dit. Comme je l'ai dit, l'erreur n'est pas dans la signature, mais dans la logique de vos décorateurs. Vous accédez inconditionnellement kwargs ['x'] lorsque kwargs n'est pas garanti de contenir 'x' comme une touche. Si la signature était le problème, vous obtiendriez un typeError pour appeler foo avec les mauvais arguments, pas un KeyError du corps de la une fonction; Le fait que le corps de la fonction exécute du tout signifie que les arguments sont acceptables en fonction de la signature.


Pour une analogie, si vous avez défini defr wrapper (* args, ** kwargs): élever ValueError () puis FOO () Souleverait une ValueError car c'est le comportement déclaré de la fonction d'emballage; Le fait que l'appel de la fonction décorée augmente une erreur ne signifie pas que sa signature est fausse.


Mais dans le décorateur intérieur, les références à la plaine kwargs sont capables d'accéder à la valeur par défaut transmise à la fonction décorée, sans utilisation de inspecter . Ce n'est que le décorateur externe qui échoue.


kwargs serait rempli avec les valeurs par défaut de la fonction emballées, si la fonction Wrapper est appelée sans arguments. Par exemple. Si wrapper wraps applico en utilisant functools.arras et func définit le mot-clé arguments avec des valeurs par défaut, alors il devrait être impossible pour Wrapper Pour toujours être appelé avec vide kwargs . Il serait toujours avoir ceux qui venaient de la fonction qu'elle enveloppe, au minimum, inévitablement.


"Mais dans le décorateur intérieur, les références à des kwargs ordinaires peuvent accéder à la valeur par défaut passée à la fonction décorée, sans utiliser d'inspection. Ce n'est que le décorateur externe qui échoue." Ce n'est tout simplement pas vrai; Si vous utilisez uniquement le @validate_y décorateur sans @validate_x alors il échoue avec KeyError: 'y' . La seule raison pour laquelle le décorateur intérieur ne soulève pas une erreur lorsque vous utilisez les deux décorateurs, c'est que le décorateur externe soulève une erreur avant d'avoir une chance d'appeler la fonction emballée interne.


J'ai modifié pour ajouter une démonstration que kwargs n'est pas rempli avec la valeur par défaut de l'argument lorsque vous n'avez qu'un décorateur.