web-dev-qa-db-fra.com

Comment créer une représentation de chaîne personnalisée pour un objet de classe?

Considérez cette classe:

class foo(object):
    pass

La représentation de chaîne par défaut ressemble à ceci:

>>> str(foo)
"<class '__main__.foo'>"

Comment puis-je faire en sorte que cet affichage soit une chaîne personnalisée?

176
Björn Pollex

Implémentez __str__() ou __repr__() dans la métaclasse de la classe.

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object):
  __metaclass__ = MC

print C

Utilisez __str__ si vous voulez une chaîne lisible, utilisez __repr__ pour des représentations non ambiguës.

241
class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"
20
Andrey Gubarev

Si vous devez choisir entre __repr__ ou __str__, optez pour le premier, comme implémentation par défaut __str__ appelle __repr__ alors qu'elle n'était pas définie.

Exemple de Vector3 personnalisé:

class Vector3(object):
    def __init__(self, args):
        self.x = args[0]
        self.y = args[1]
        self.z = args[2]

    def __repr__(self):
        return "Vector3([{0},{1},{2}])".format(self.x, self.y, self.z)

    def __str__(self):
        return "x: {0}, y: {1}, z: {2}".format(self.x, self.y, self.z)

Dans cet exemple, repr renvoie à nouveau une chaîne pouvant être directement consommée/exécutée, alors que str est plus utile en tant que sortie de débogage.

v = Vector3([1,2,3])
print repr(v)    #Vector3([1,2,3])
print str(v)     #Vector(x:1, y:2, z:3)
10
user1767754

La réponse approuvée d'Ignacio Vazquez-Abrams a tout à fait raison. Il s'agit toutefois de la génération Python 2. Une mise à jour pour le Python 3 actuellement en cours serait:

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object, metaclass=MC):
    pass

print(C)

Si vous voulez un code qui traverse Python 2 et Python 3, le module six vous a couvert:

from __future__ import print_function
from six import with_metaclass

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(with_metaclass(MC)):
    pass

print(C)

Enfin, si vous voulez une repro statique sur mesure pour une classe, l’approche basée sur la classe ci-dessus fonctionne très bien. Mais si vous en avez plusieurs, vous devrez générer une métaclasse semblable à MC pour chaque élément, ce qui peut être fastidieux. Dans ce cas, aller plus loin dans votre métaprogrammation et créer une usine de métaclasses rend les choses un peu plus propres:

from __future__ import print_function
from six import with_metaclass

def custom_class_repr(name):
    """
    Factory that returns custom metaclass with a class ``__repr__`` that
    returns ``name``.
    """
    return type('whatever', (type,), {'__repr__': lambda self: name})

class C(with_metaclass(custom_class_repr('Wahaha!'))): pass

class D(with_metaclass(custom_class_repr('Booyah!'))): pass

class E(with_metaclass(custom_class_repr('Gotcha!'))): pass

print(C, D, E)

impressions:

Wahaha! Booyah! Gotcha!

La métaprogrammation n'est pas une tâche dont vous avez généralement besoin tous les jours, mais quand vous en avez besoin, elle frappe vraiment!

3
Jonathan Eunice

En ajoutant à toutes les bonnes réponses, ma version avec décoration:

from __future__ import print_function
import six

def classrep(rep):
    def decorate(cls):
        class RepMetaclass(type):
            def __repr__(self):
                return rep

        class Decorated(six.with_metaclass(RepMetaclass, cls)):
            pass

        return Decorated
    return decorate


@classrep("Wahaha!")
class C(object):
    pass

print(C)

stdout:

Wahaha!

Les inconvénients:

  1. Vous ne pouvez pas déclarer C sans une super classe (no class C:)
  2. Les instances C seront des instances d'une dérivation étrange, c'est donc probablement une bonne idée d'ajouter un __repr__ pour les instances également.
0
Aviv Goll