Строковые представления экземпляров класса: методы __str__ и __repr__

Введение

Примеры

  • 1

    мотивация

    Итак, вы только что создали свой первый класс в 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)
    
     

    Мы увидим, что наш вывод намного лучше:

     Туз пик 

    Так здорово, мы закончили, верно?

    Ну просто , чтобы покрыть наши базы, давайте двойную проверку , что мы решили первый вопрос , который мы столкнулись, печать списка 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__ методы. (Подробнее о том, как вы можете использовать эти два метода по-разному ниже).

  • 0

    Реализованы оба метода, стиль 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) 

Синтаксис

Параметры

Примечания