Как создать собственное строковое представление для объекта класса?

202

Рассмотрим этот класс:

class foo(object):
    pass

Строковое представление по умолчанию выглядит примерно так:

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

Как я могу сделать это отображение пользовательской строки?

Бьёрн Поллекс
источник

Ответы:

273

Реализуйте __str__()или __repr__()в метаклассе класса.

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

class C(object):
  __metaclass__ = MC

print C

Используйте, __str__если вы имеете в виду читаемую строку, используйте __repr__для однозначных представлений.

Игнасио Васкес-Абрамс
источник
2
Я хотел бы создать некоторый декоратор классов, чтобы я мог легко устанавливать собственные строковые представления для своих классов без необходимости писать метакласс для каждого из них. Я не очень знаком с метаклассами Python, так что вы можете дать мне какие-нибудь подсказки там?
Бьорн Поллекс
К сожалению, этого нельзя сделать с помощью декораторов классов; это должно быть установлено, когда класс определен.
Игнасио Васкес-Абрамс
10
@ Space_C0wb0y: Вы можете добавить строку как _representationв тело класса, так и return self._representationв __repr__()метод метакласса.
Свен Марнах
@ BjörnPollex Вы могли бы справиться с этим с помощью декоратора, но я ожидаю, что вам придется бороться со многими тонкостями языка. И даже если вы это сделаете, вы все равно будете использовать метакласс тем или иным образом, поскольку вы не хотите использовать представление по умолчанию __repr__для представления C. Альтернативой наличию _representationчлена является создание фабрики метаклассов, которая производит метакласс с правильным __repr__(это может быть хорошо, если вы часто используете это).
скайкинг
2
@ThomasLeonard: stackoverflow.com/questions/39013249/metaclass-in-python3-5
Игнасио Васкес-Абрамс
22
class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"
Андрей Губарев
источник
10
Это изменяет строковое представление для instancesкласса, а не для самого класса.
Тауран
извините, не видит вторую часть вашего поста. Используйте метод выше.
Андрей Губарев
6
@RobertSiemer Почему? Хотя его ответ конкретно не нацелен на вопрос ОП, он все же полезен. Это помогло мне. И на первый взгляд, я не вижу вопросов, требующих реализации экземпляра. Поэтому, вероятно, люди сначала попадают на эту страницу.
akinuri
14

Если вам нужно выбрать между __repr__или __str__перейти к первому, как по умолчанию __str__вызовы реализации, __repr__когда он не был определен.

Пользовательский пример 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)

В этом примере reprснова возвращает строку, которая может быть непосредственно использована / выполнена, тогда strкак более полезна в качестве отладочного вывода.

v = Vector3([1,2,3])
print repr(v)    #Vector3([1,2,3])
print str(v)     #x:1, y:2, z:3
user1767754
источник
1
Хотя ваша точка зрения на __repr__vs __str__верна, это не дает ответа на реальный вопрос, касающийся объектов класса, а не экземпляров.
Бьорн Поллекс
Спасибо за отзыв, полностью за этим следил. Позвольте мне пересмотреть мой ответ.
user1767754
Я думаю, что ваши реализации для repr и str поменялись местами.
Jspencer
4

Одобренный ответ Игнасио Васкеса-Абрамса совершенно прав. Это, однако, из поколения Python 2. Обновление для текущего Python 3 будет:

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

class C(object, metaclass=MC):
    pass

print(C)

Если вам нужен код, который работает как на Python 2, так и на Python 3, то шесть модулей помогут вам:

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)

Наконец, если у вас есть один класс, для которого вы хотите иметь пользовательское статическое repr, вышеописанный подход на основе классов работает отлично. Но если у вас их несколько, вам нужно сгенерировать метакласс, аналогичный MCкаждому, и это может стать утомительным. В этом случае продвижение метапрограммирования на шаг вперед и создание фабрики метаклассов делает вещи немного чище:

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)

печатает:

Wahaha! Booyah! Gotcha!

Метапрограммирование - это не то, что вам обычно нужно каждый день, но когда вам это нужно, оно действительно попадает в точку!

Джонатан Юнис
источник
0

Просто добавлю ко всем прекрасным ответам мою версию с оформлением:

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)

стандартный вывод:

Wahaha!

Недостатки:

  1. Вы не можете объявить Cбез суперкласса (нет class C:)
  2. Cэкземпляры будут экземплярами некоторого странного происхождения, так что, вероятно, будет хорошей идеей добавить и __repr__для экземпляров.
Авив Голл
источник