Jak utworzyć niestandardową reprezentację ciągu dla obiektu klasy?

202

Rozważ tę klasę:

class foo(object):
    pass

Domyślna reprezentacja ciągu wygląda mniej więcej tak:

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

Jak ustawić wyświetlanie niestandardowego ciągu?

Björn Pollex
źródło

Odpowiedzi:

272

Implementuj __str__()lub __repr__()w metaklasie klasy.

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

class C(object):
  __metaclass__ = MC

print C

Użyj, __str__jeśli masz na myśli czytelne oznaczenie, użyj __repr__do jednoznacznych reprezentacji.

Ignacio Vazquez-Abrams
źródło
2
Chciałbym stworzyć pewnego rodzaju dekorator klas, dzięki czemu mogę łatwo ustawić niestandardowe reprezentacje ciągów dla moich klas bez konieczności pisania metaklasy dla każdej z nich. Nie znam się dobrze na metaklasach Pythona, więc czy możesz podać mi jakieś wskazówki?
Björn Pollex
Niestety nie można tego zrobić z dekoratorami klas; należy go ustawić, gdy klasa jest zdefiniowana.
Ignacio Vazquez-Abrams
10
@ Space_C0wb0y: można dodać ciąg podobny _representationdo ciała klasy i return self._representationw __repr__()sposobie metaklasą.
Sven Marnach,
@ BjörnPollex Być może uda ci się to z dekoratorem, ale spodziewam się, że będziesz musiał zmagać się z wieloma subtelnościami języka. I nawet jeśli to zrobisz, nadal będziesz musiał używać metaklasy w taki czy inny sposób, ponieważ nie chcesz używać domyślnej __repr__reprezentacji C. Alternatywą dla posiadania _representationczłonka jest utworzenie fabryki metaklasy, która produkuje metaklasę z odpowiednią __repr__(może to być miłe, jeśli często z niej korzystasz).
skyking
2
@ThomasLeonard: stackoverflow.com/questions/39013249/metaclass-in-python3-5
Ignacio Vazquez-Abrams
22
class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"
Andrey Gubarev
źródło
10
To zmienia ciąg znaków reprezentujący instancesklasę, a nie samą klasę.
tauran
przepraszam, nie widzę drugiej części twojego postu. Użyj powyższej metody.
Andrey Gubarev
6
@RobertSiemer Dlaczego? Chociaż jego odpowiedź nie jest konkretnie ukierunkowana na pytanie PO, nadal jest pomocna. Pomogło mi to. I na pierwszy rzut oka nie widzę żadnych pytań dotyczących implementacji. Prawdopodobnie ludzie najpierw lądują na tej stronie.
akinuri
14

Jeśli musisz wybrać między pierwszym __repr__lub __str__przejść do pierwszego, domyślnie __str__wywołuje implementację, __repr__gdy nie została zdefiniowana.

Przykład niestandardowego Vector3:

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)

W tym przykładzie reprponownie zwraca ciąg, który może być bezpośrednio wykorzystany / wykonany, ale strjest bardziej przydatny jako wyjście debugowania.

v = Vector3([1,2,3])
print repr(v)    #Vector3([1,2,3])
print str(v)     #x:1, y:2, z:3
użytkownik1767754
źródło
1
Chociaż twoje zdanie na temat __repr__vs __str__jest poprawne, to nie odpowiada na rzeczywiste pytanie, które dotyczy obiektów klasy, a nie instancji.
Björn Pollex
Dzięki za opinie, całkowicie to nadzorowałem. Pozwól, że przejrzę moją odpowiedź.
user1767754,
Myślę, że implementacje dla repr i str zostały zamienione.
jspencer
4

Zatwierdzona odpowiedź Ignacio Vazquez-Abramsa jest całkiem słuszna. Jest jednak z generacji Python 2. Aktualizacja obecnego obecnie języka Python 3 będzie:

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

class C(object, metaclass=MC):
    pass

print(C)

Jeśli chcesz, aby kod działał zarówno w Pythonie 2, jak i Pythonie 3, sześć modułów obejmuje:

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)

Wreszcie, jeśli masz jedną klasę, w której chcesz mieć niestandardowe statyczne powtórzenie, powyższe podejście oparte na klasach działa świetnie. Ale jeśli masz kilka, musisz wygenerować metaklasę podobną do MCkażdej z nich, a to może być męczące. W takim przypadku pójście o krok dalej i stworzenie fabryki metaklasy sprawi, że wszystko będzie trochę czystsze:

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)

drukuje:

Wahaha! Booyah! Gotcha!

Metaprogramowanie nie jest czymś, czego zwykle potrzebujesz na co dzień - ale kiedy tego potrzebujesz, naprawdę trafia w sedno!

Jonathan Eunice
źródło
0

Właśnie dodając do wszystkich dobrych odpowiedzi, moja wersja z dekoracją:

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)

standardowe:

Wahaha!

Wady:

  1. Nie możesz zadeklarować Cbez superklasy (nie class C:)
  2. Cinstancje będą instancjami o dziwnym pochodzeniu, więc prawdopodobnie dobrym pomysłem jest dodanie również __repr__dla instancji.
Aviv Goll
źródło