Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

Чем отличается __repr__ от __str__?

Возьмем как пример парочку выдуманных классов:
import requests
class A: def __init__(self, a="string", b=10, c=["a", "b", "c", 1, 2, 3]): # параметр c - произвольной длины self.a = a self.b = b self.c = c
class B: def __init__(self, A_object): # A_object - объект типа A self.neighbor = A_object self.session = requests.Session()
1) Как следовало бы написать __repr__ и __str__ для этих классов?
2) Документация говорит о том, что __repr__ - это однозначное представление объекта в виде строки, которое можно использовать, чтобы воссоздать точно такой же объект, а если это невозможно, то вывести какое-нибудь полезное сообщение. Очень похоже, что требуется сериализовать объект таким образом. В чем же тогда отличие от связки __getstate__, __setstate__? Эти два метода тоже могут запросто строку вернуть и требования у этой строки такие же - однозначное представление объекта. Зачем же дублирование?
И наоборот, как замену getstate, setstate можно придумать следующее:
def __repr__(self): return "A({a}, {b}, {c})".format(a=self.a, b=self.b, c=self.c)
а потом вызывать это:
A1 = A() A2 = eval(A1.__repr__())
3) Та же самая документация говорит, что __str__ - должен вывести красивое, читабельное информационное сообщение, отражающее объект. Как тогда различить результат этого метода и какое-нибудь полезное сообщение из __repr__?
4) Далеко не всегда можно представить какой-то объект в виде строки. Например, функции, длинные коллекции, другие объекты без str и без repr. Выходит, что в этом случае либо не выполняется требование __repr__, либо необходимо для всех подобных объектов вывести содержание __dict__, что хранит значение всех-всех методов и переменных. Выглядит это решение плоховато, что же делать?
5) Возвращаясь к документации __repr__ - можно пример ситуации, когда невозможно однозначно представить объект как строку? Объекты ведь не имеют к квантовому миру никакого отношения - они ВСЕГДА детерминированы, всегда есть набор переменных и набор методов. но не зря же в документации эта строка?
6) А еще есть __format__, который тоже должен возвращать строку...


Ответ

datetime.date хорошо иллюстрирует разницу:
>>> from datetime import date >>> date.today() # sys.displayhook() uses repr() by default datetime.date(2016, 6, 13) >>> print(date.today()) # print uses str() here 2016-06-13
repr(obj) возвращает однозначное текстовое представление (representation) объекта полезное для отладки, сообщений об ошибках, REPL, которое (иногда) позволяет его (теоретически) восстановить: eval(repr(obj)) == obj
str(obj) возвращает читаемый текст. Для многих объектов имеет смысл определить __repr__() (так как реализация по умолчанию в object.__repr__ не слишком информативна). __str__() имеет смысл определять для объектов, для которых существует «естественное» человеко-читаемое (неспецифичное для Питона) представление (как в примере с датой), например, для логов.
Если __str__ не определён, то str() использует repr()
Разные форматы рассчитаны на разного потребителя (человек/программа, Питон/общее назначение). Вот график, иллюстрирующий когда разные форматы удобно использовать:
^ ^ | | Более дружелюбный для человека | +---+ | |str| | +---+ | | +----+ +----+ | |repr| |json| | +----+ +----+ | | +------+ | |pickle| | +------+ | | | Машино-читаемый +--------------------------------->
Специфичный для Питона -> Более общий
1) Как следовало бы написать __repr__ и __str__ для этих классов?
class A: def __repr__(self): return "A(%r, %r, %r)" % (self.a, self.b, self.c)
class B: def __repr__(self): return "" % (self.neighbor, self.session)
Объекты B не могут быть восстановлены из repr()
2) Документация говорит о том, что __repr__ - это однозначное представление объекта в виде строки, которое можно использовать, чтобы воссоздать точно такой же объект, а если это невозможно, то вывести какое-нибудь полезное сообщение. Очень похоже, что требуется сериализовать объект таким образом. В чем же тогда отличие от связки __getstate__, __setstate__? Эти два метода тоже могут запросто строку вернуть и требования у этой строки такие же - однозначное представление объекта. Зачем же дублирование?
pickle (__getstate__/__setstate__) не является человеко-читаемым форматом. Цели и ограничения на дизайн другие. То что eval(repr(obj)) иногда работает, ещё не значит что это хорошая идея использовать это вместо pickle.loads(pickle.dumps(obj)) или json.loads(json.dumps(obj)).
Основной потребитель repr() это человек. Потребитель pickle.dumps() это как правило программа, например, multiprocessing использует pickle для обмена данными между процессами.
3) Та же самая документация говорит, что __str__ - должен вывести красивое, читабельное информационное сообщение, отражающее объект. Как тогда различить результат этого метода и какое-нибудь полезное сообщение из __repr__?
Если вы не видите имя типа, то это не __repr__() (за очевидным исключением констант (Python literals) таких как 'abc', 123).
4) Далеко не всегда можно представить какой-то объект в виде строки. Например, функции, длинные коллекции, другие объекты без str и без repr. Выходит, что в этом случае либо не выполняется требование __repr__, либо необходимо для всех подобных объектов вывести содержание __dict__, что хранит значение всех-всех методов и переменных. Выглядит это решение плоховато, что же делать?
eval(repr(obj)) это подсказка, а не требование: если объект большой, то очевидно нужно сократить его представление. Во многих случаях, можно использовать многоточие:
>>> numpy.arange(10000000) array([ 0, 1, 2, ..., 9999997, 9999998, 9999999]) >>> print(numpy.arange(10000000)) [ 0 1 2 ..., 9999997 9999998 9999999]
Ещё раз: потребитель repr()—программист Питона, который видит это представление в REPL или debugger.
5) Возвращаясь к документации __repr__ - можно пример ситуации, когда невозможно однозначно представить объект как строку? Объекты ведь не имеют к квантовому миру никакого отношения - они ВСЕГДА детерминированы, всегда есть набор переменных и набор методов. но не зря же в документации эта строка?
Документация говорит про случай, когда eval(repr(obj)) == obj не имеет смысла:
>>> object()
6) А еще есть __format__, который тоже должен возвращать строку...
Этот специальный метод позволяет типу переопределить как format() себя ведёт, например:
>>> "{:%Y-%m-%d}".format(datetime.today()) '2016-06-13'
В Питоне 2 есть __unicode__() как __str__(), но unicode возвращает. А ещё __fspath__() (PEP 519) появился. Могут быть другие протоколы, которые возвращают строки (со своим обоснованием).

Комментариев нет:

Отправить комментарий