Строковые представления экземпляров класса: методы __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__ возвращает строку.).

__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__ метод вызывается , когда мы проходили мимо нашей Card экземпляра print и __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__ реализации и использовать.

Так, чтобы было ясно, рассмотрим следующую версию 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__ метода. Если память служит, во время разговора Raymond Hettinger сказал , что обеспечение реализации классов __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)