2017-07-31 2 views
2

J'ai une collection de fonctions avec (principalement) des paramètres partagés mais des processus différents. J'aimerais utiliser un décorateur pour ajouter la description de chaque paramètre à la docstring de niveau titre d'une fonction.Décorateurs avec paramètres

J'ai essayé d'imiter la structure trouvée dans this answer en incorporant une fonction imbriquée dans appender mais échoué. J'ai également essayé functools.partial mais quelque chose est légèrement éteint.

Ma tentative:

def appender(func, *args): 
    """Appends additional parameter descriptions to func's __doc__.""" 
    def _doc(func): 
     params = ''.join([defaultdocs[arg] for arg in args]) 
     func.__doc__ += '\n' + params 
     return func 
    return _doc 

defaultdocs = { 

    'a' : 
    """ 
    a : int, default 0 
     the first parameter 
    """, 

    'b' : 
    """ 
    b : int, default 1 
     the second parameter 
    """ 
    } 

@appender('a') 
def f(a): 
    """Title-level docstring.""" 
    return a 

@appender('a', 'b') 
def g(a, b): 
    """Title-level docstring.""" 
    return a + b 

Cela échoue, et elle échoue, je crois que le premier arg passé à appender est interprété comme func. Alors, quand je vois le résultat docstring pour g je reçois:

print(g.__doc__) 
Title-level docstring. 

    b : int, default 1 
     the second parameter 

parce que, encore une fois, 'a' est interprété comme 'func' quand je veux que ce soit le premier élément de *args. Comment puis-je corriger cela?

Résultat souhaité:

print(g.__doc__) 
Title-level docstring. 

    a : int, default 0 
     the first parameter 

    b : int, default 1 
     the second parameter 

Répondre

4

Cela se produit parce que les noms de variables que vous passez obtenir Capturé en fait dans un argument func.

Pour faire décorateurs appelables en Python vous devez coder la fonction deux fois, ayant fonction externe pour accepter des arguments de décorateur et fonction interne pour accepter la fonction d'origine. Décorateurs Callable sont juste des fonctions d'ordre supérieur qui retournent d'autres décorateurs. Par exemple:

def appender(*args): # This is called when a decorator is called, 
         # e. g. @appender('a', 'b') 
    """Appends additional parameter descriptions to func's __doc__.""" 
    def _doc(func): # This is called when the function is about 
        # to be decorated 
     params = ''.join([defaultdocs[arg] for arg in args]) 
     func.__doc__ += '\n' + params 
     return func 
    return _doc 

La fonction externe (appender) agit comme une usine pour le nouveau décorateur en fonction _doc est un décorateur réel. Passez toujours cette façon:

  • décorateur Pass args à la fonction externe
  • Passe fonction initiale à la fonction interne

Une fois que le Python voit ceci:

@appender('a', 'b') 
def foo(): pass 

.. .il fera quelque chose comme ça sous le capot:

foo = appender('a', 'b')(foo) 

... qui se développe à ceci:

decorator = appender('a', 'b') 
foo = decorator(foo) 

En raison de la façon dont scopes dans le travail Python, chaque nouveau retour _doc instance de fonction aura sa propre valeur args locale de la fonction externe.

1

Une solution alternative qui utilise inspect.signature pour collecter les paramètres de fonction transmis.

import inspect 
import textwrap 

def appender(defaultdocs): 
    def _doc(func): 
     params = inspect.signature(func).parameters 
     params = [param.name for param in params.values()] 
     params = ''.join([textwrap.dedent(defaultdocs[param]) 
         for param in params]) 
     func.__doc__ += '\n\nParameters\n' + 10 * '=' + params 
     return func 

    return _doc 

Exemple:

# default docstrings for parameters that are re-used often 
# class implementation not a good alternative in my specific case 

defaultdocs = { 

    'a' : 
    """ 
    a : int, default 0 
     the first parameter""", 

    'b' : 
    """ 
    b : int, default 1 
     the second parameter""" 
    } 

@appender 
def f(a): 
    """Title-level docstring.""" 
    return a 

@appender 
def g(a, b): 
    """Title-level docstring.""" 
    return a + b 

Cette description pour ajoute les a et b à g.__doc__ sans avoir besoin de les préciser dans le décorateur:

help(g) 
Help on function g in module __main__: 

g(a, b) 
    Title-level docstring. 

    Parameters 
    ========== 
    a : int, default 0 
     the first parameter 
    b : int, default 1 
     the second parameter