Python Language
Строковые представления экземпляров класса: методы __str__ и __repr__
Поиск…
замечания
Заметка об использовании обоих методов
Когда оба метода реализованы, довольно часто возникает метод __str__
который возвращает удобное для человека представление (например, «Ace of Spaces»), а __repr__
возвращает представление, __repr__
на eval
.
Фактически, документы Python для repr()
отмечают именно это:
Для многих типов эта функция пытается вернуть строку, которая даст объект с тем же значением при передаче в eval (), иначе представление представляет собой строку, заключенную в угловые скобки, которая содержит имя типа объекта вместе с дополнительной информацией, которая часто включает имя и адрес объекта.
Это означает, что __str__
может быть реализован, чтобы вернуть что-то вроде «Ace of Spaces», как показано ранее, __repr__
может быть реализовано вместо возврата Card('Spades', 1)
Эта строка может быть передана непосредственно в eval
в виде «кругового путешествия»:
object -> string -> object
Примером реализации такого метода может быть:
def __repr__(self):
return "Card(%s, %d)" % (self.suit, self.pips)
Заметки
[1] Этот вывод специфичен для реализации. Строка отображается из cpython.
[2] Возможно, вы уже видели результат этого разделения str()
/ repr()
и не знали его. Когда строки, содержащие специальные символы, такие как обратные косые черты, преобразуются в строки через str()
обратные косые черты появляются как есть (они появляются один раз). Когда они преобразуются в строки с помощью функции repr()
(например, в качестве элементов отображаемого списка), обратные косы сбрасываются и, таким образом, отображаются дважды.
мотивация
Таким образом, вы только что создали свой первый класс в Python, аккуратном небольшом классе, который инкапсулирует игровую карту:
class Card:
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
В другом месте вашего кода вы создаете несколько экземпляров этого класса:
ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)
Вы даже создали список карточек, чтобы представить «руку»:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
Теперь, во время отладки, вы хотите увидеть, как выглядит ваша рука, поэтому вы делаете то, что приходит естественно и пишите:
print(my_hand)
Но то, что вы возвращаете, - это куча тарабарщин:
[<__main__.Card instance at 0x0000000002533788>,
<__main__.Card instance at 0x00000000025B95C8>,
<__main__.Card instance at 0x00000000025FF508>]
Смущенный, вы пытаетесь просто распечатать одну карту:
print(ace_of_spades)
И снова вы получаете этот странный результат:
<__main__.Card instance at 0x0000000002533788>
Не бойся. Мы собираемся это исправить.
Во-первых, однако, важно понять, что здесь происходит. Когда вы написали print(ace_of_spades)
вы сказали Python, что хотите распечатать информацию о экземпляре Card
ваш код вызывает ace_of_spades
. И, честно говоря, так оно и было.
Этот вывод состоит из двух важных бит: type
объекта и id
объекта. Только вторая часть (шестнадцатеричное число) достаточно, чтобы однозначно идентифицировать объект во время вызова print
. [1]
Что на самом деле произошло, так это то, что вы попросили Python «положить в слова» суть этого объекта, а затем отобразить его для вас. Более явная версия того же механизма может быть:
string_of_card = str(ace_of_spades)
print(string_of_card)
В первой строке вы пытаетесь превратить экземпляр своей Card
в строку, а во втором - ее.
Эта проблема
Проблема, с которой вы сталкиваетесь, возникает из-за того, что, хотя вы сказали Python обо всем, что вам нужно знать о классе Card
для создания карт, вы не сказали ему, как вы хотели, чтобы экземпляры Card
были преобразованы в строки.
И поскольку он не знал, когда вы (неявно) написали str(ace_of_spades)
, он дал вам то, что вы видели, общее представление экземпляра Card
.
Решение (часть 1)
Но мы можем сказать Python, как мы хотим, чтобы экземпляры наших пользовательских классов были преобразованы в строки. И способ, которым мы это делаем, - это __str__
«dunder» (для двойного подчеркивания) или «волшебный» метод.
Всякий раз, когда вы указываете Python на создание строки из экземпляра класса, он будет искать метод __str__
в классе и вызывать его.
Рассмотрим следующую обновленную версию нашего класса Card
:
class Card:
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
def __str__(self):
special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}
card_name = special_names.get(self.pips, str(self.pips))
return "%s of %s" % (card_name, self.suit)
Здесь мы теперь определили метод __str__
на нашем классе Card
который после простого поиска словаря для лицевых карточек возвращает строку, форматированную, однако мы решаем.
(Обратите внимание, что здесь str(ace_of_spades)
, чтобы подчеркнуть важность возврата строки, а не просто ее печать. Печать может показаться str(ace_of_spades)
, но тогда вы будете распечатывать карту, когда вы сделали что-то вроде str(ace_of_spades)
, даже не имея вызова функции печати в вашей основной программе. Поэтому, чтобы быть понятным, убедитесь, что __str__
возвращает строку.).
Метод __str__
- это метод, поэтому первый аргумент будет self
и он не должен принимать и не принимать дополнительные аргументы.
Возвращаясь к нашей проблеме отображения карты более удобным образом, если мы снова запустим:
ace_of_spades = Card('Spades', 1)
print(ace_of_spades)
Мы увидим, что наш результат намного лучше:
Ace of Spades
Настолько замечательно, что мы закончили, да?
Ну, просто чтобы покрыть наши базы, давайте дважды проверем, что мы решили первую проблему, с которой мы столкнулись, распечатав список экземпляров Card
, hand
.
Поэтому мы перепроверем следующий код:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)
И, к нашему удивлению, мы снова получаем эти смешные шестнадцатеричные коды:
[<__main__.Card instance at 0x00000000026F95C8>,
<__main__.Card instance at 0x000000000273F4C8>,
<__main__.Card instance at 0x0000000002732E08>]
В чем дело? Мы сказали Python, что мы хотим, чтобы наши экземпляры Card
отображались, почему они, похоже, не забыли?
Решение (часть 2)
Ну, закулисный механизм немного отличается, когда Python хочет получить строковое представление элементов в списке. Оказывается, Python не заботится о __str__
для этой цели.
Вместо этого, он ищет другой метод, __repr__
, и если это не найдено, он возвращается на «шестнадцатеричном вещь». [2]
Значит, вы говорите, что я должен сделать два метода, чтобы сделать то же самое? Один из них, когда я хочу print
свою карточку сам по себе, а другой, когда он находится в каком-то контейнере?
Нет, но сначала давайте посмотрим, каким будет наш класс , если бы мы реализовали __str__
и __repr__
:
class Card:
special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
def __str__(self):
card_name = Card.special_names.get(self.pips, str(self.pips))
return "%s of %s (S)" % (card_name, self.suit)
def __repr__(self):
card_name = Card.special_names.get(self.pips, str(self.pips))
return "%s of %s (R)" % (card_name, self.suit)
Здесь реализация двух методов __str__
и __repr__
точно такая же, за исключением того, что для различения двух методов (S)
добавляется к строкам, возвращаемым __str__
и (R)
добавляется к строкам, возвращаемым __repr__
.
Обратите внимание, что как и наш метод __str__
, __repr__
принимает аргументов и возвращает строку.
Теперь мы можем видеть, какой метод отвечает за каждый случай:
ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand) # [Ace of Spades (R), 4 of Clubs (R), 6 of Hearts (R)]
print(ace_of_spades) # Ace of Spades (S)
Как было __str__
метод __str__
вызывался, когда мы передавали экземпляр нашей Card
для print
и __repr__
метод __repr__
когда мы передавали список наших экземпляров для print
.
На этом этапе стоит отметить, что так же, как мы можем явно создать строку из экземпляра пользовательского класса, используя str()
как это было ранее, мы также можем явно создать строковое представление нашего класса со встроенной функцией, называемой repr()
.
Например:
str_card = str(four_of_clubs)
print(str_card) # 4 of Clubs (S)
repr_card = repr(four_of_clubs)
print(repr_card) # 4 of Clubs (R)
И дополнительно, если они определены, мы могли бы напрямую вызвать методы (хотя это кажется немного неясным и ненужным):
print(four_of_clubs.__str__()) # 4 of Clubs (S)
print(four_of_clubs.__repr__()) # 4 of Clubs (R)
Об этих дублированных функциях ...
Разработчики Python поняли, что в случае, если вы хотите, чтобы идентичные строки были возвращены из str()
и repr()
вам, возможно, придется использовать функционально-дублирующие методы - что-то не нравится.
Поэтому вместо этого существует механизм для устранения необходимости в этом. Один из них я протащил тебя до этого момента. Оказывается, если класс реализует метод __repr__
но не метод __str__
, и вы передаете экземпляр этого класса в str()
(неявно или явно), Python будет __repr__
от вашей реализации __repr__
и использовать это.
Итак, чтобы быть понятным, рассмотрите следующую версию класса Card
:
class Card:
special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
def __repr__(self):
card_name = Card.special_names.get(self.pips, str(self.pips))
return "%s of %s" % (card_name, self.suit)
Обратите внимание , эта версия только реализует __repr__
метод. Тем не менее, вызовы str()
приводят к удобной версии:
print(six_of_hearts) # 6 of Hearts (implicit conversion)
print(str(six_of_hearts)) # 6 of Hearts (explicit conversion)
как и вызовы repr()
:
print([six_of_hearts]) #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts)) # 6 of Hearts (explicit conversion)
Резюме
Чтобы вы могли расширять возможности своих экземпляров класса, чтобы «показать себя» в удобных для пользователя целях, вам нужно рассмотреть возможность внедрения, по крайней мере, метода __repr__
вашего класса. Если память используется, во время разговора Раймонд Хеттингер сказал, что обеспечение классов реализует __repr__
- это одна из первых вещей, которую он ищет при выполнении обзоров кода Python, и теперь должно быть понятно, почему. Объем информации, которую вы могли бы добавить к отладкам, отчетам о сбоях или файлам журнала с помощью простого метода, является огромным по сравнению с ничтожной и часто менее полезной (тип, идентификатор) информацией, которая предоставляется по умолчанию.
Если вам нужны разные представления для того, когда, например, внутри контейнера, вы захотите реализовать методы __repr__
и __str__
. (Подробнее о том, как вы можете использовать эти два метода по-разному ниже).
Оба метода реализованы, стиль eval-round-trip __repr __ ()
class Card:
special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
# Called when instance is converted to a string via str()
# Examples:
# print(card1)
# print(str(card1)
def __str__(self):
card_name = Card.special_names.get(self.pips, str(self.pips))
return "%s of %s" % (card_name, self.suit)
# Called when instance is converted to a string via repr()
# Examples:
# print([card1, card2, card3])
# print(repr(card1))
def __repr__(self):
return "Card(%s, %d)" % (self.suit, self.pips)