Comment implémentez-vous str pour une fonction?

Étant donné une fonction foo:

def foo(x):
     pass

L'Impression de sa représentation en invoquant str ou repr vous donne quelque chose d'ennuyeux comme ceci:

str(foo)
'<function foo at 0x119e0c8c8>'

J'aimerais savoir s'il est possible de remplacer la méthode __str__ d'une fonction pour imprimer autre chose. Essentiellement, je voudrais faire:

str(foo)
"I'm foo!'

Maintenant, je comprends que la description d'une fonction devrait provenir de __doc__ qui est la docstring de la fonction. Cependant, ce n'est qu'une expérience.

En essayant de trouver une solution à ce problème, je suis tombé sur l'implémentation __str__ pour classes: Comment définir une méthode __str__ pour une classe?

Cette approche consistait à définir une métaclasse avec une méthode __str__, puis à essayer d'assigner le crochet __metaclass__ dans la classe réelle.

Je me demandais si la même chose pouvait être faite à la classe function, alors voici ce que j'ai essayé -

In [355]: foo.__class__
Out[355]: function

In [356]: class fancyfunction(type):
     ...:     def __str__(self):
     ...:         return self.__name__
     ...:     

In [357]: foo.__class__.__metaclass__ = fancyfunction
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

Je pensais que ça ne marcherait pas, mais ça valait le coup d'essayer!

Alors, qu'est-ce que la meilleure façon d'implémenter __str__ pour une fonction?

26
demandé sur coldspeed 2017-11-23 12:56:25

2 réponses

Une fonction en Python est juste un objet appelable. Utiliser def pour définir une fonction est un moyen de créer un tel objet. Mais rien ne vous empêche en fait de créer un type appelable et d'en créer une instance pour obtenir une fonction.

Donc, les deux choses suivantes sont fondamentalement égales:

def foo ():
    print('hello world')


class FooFunction:
    def __call__ (self):
        print('hello world')

foo = FooFunction()

Sauf que le dernier nous permet évidemment de définir les méthodes spéciales du type de fonction, comme __str__ et __repr__.

class FooFunction:
    def __call__ (self):
        print('hello world')

    def __str__ (self):
        return 'Foo function'

foo = FooFunction()
print(foo) # Foo function

Mais créer un type juste car cela devient un peu fastidieux et il est également plus difficile de comprendre ce que fait la fonction: Après tout, la syntaxe def nous permet de juste définir le corps de la fonction. Si nous voulons continuer dans cette voie!

, Heureusement, Python a cette grande fonctionnalité appelée décorateurs que nous pouvons utiliser ici. Nous pouvons créer un décorateur de fonction qui enveloppera n'importe quelle fonction dans un type personnalisé qui appelle une fonction personnalisée pour le __str__. Cela pourrait ressembler à ceci:

def with_str (str_func):
    def wrapper (f):
        class FuncType:
            def __call__ (self, *args, **kwargs):
                # call the original function
                return f(*args, **kwargs)
            def __str__ (self):
                # call the custom __str__ function
                return str_func()

        # decorate with functool.wraps to make the resulting function appear like f
        return functools.wraps(f)(FuncType())
    return wrapper

On peut ensuite, utilisez-le pour ajouter une fonction __str__ à n'importe quelle fonction en la décorant simplement. Cela ressemblerait à ceci:

def foo_str ():
    return 'This is the __str__ for the foo function'

@with_str(foo_str)
def foo ():
    print('hello world')
>>> str(foo)
'This is the __str__ for the foo function'
>>> foo()
hello world

, Évidemment, cela a certaines limites et inconvénients puisque vous ne pouvez pas exactement reproduire ce que def ferait pour une nouvelle fonction à l'intérieur que décorateur.

Par exemple, l'utilisation du module inspect pour regarder les arguments ne fonctionnera pas correctement: pour le type appelable, il inclura l'argument self et lors de l'utilisation du décorateur Générique, il ne pourra rapporter que les détails de wrapper. Cependant, il existe peut-être certains solutions, par exemple discuté dans cette question, qui vous permettra de restaurer certains de la fonctionnalité.

, Mais cela signifie généralement que vous investissez beaucoup d'effort juste pour obtenir un __str__ travailler sur un objet de fonction qui sera probablement très rarement utilisé. Vous devriez donc vous demander si vous avez réellement besoin d'une implémentation __str__ pour vos fonctions, et quel type de les opérations que vous ferez sur ces fonctions.

28
répondu poke 2017-11-23 11:20:14

Si vous vous trouvez en train d'emballer des fonctions, il est utile de regarder functools.partial. C'est principalement pour lier des arguments bien sûr, mais c'est facultatif. C'est aussi une classe qui enveloppe les fonctions, en supprimant le Passe-Partout de le faire à partir de zéro.

from functools import partial

class foo(partial):
    def __str__(self):
        return "I'm foo!"

@foo
def foo():
    pass

assert foo() is None
assert str(foo) == "I'm foo!"
10
répondu A. Coady 2018-01-16 01:51:14