Szukaj…


Uwagi

Uwaga na temat wdrażania obu metod

Kiedy obie metody są realizowane, to dość powszechne mieć __str__ metodę, która zwraca reprezentację ludzki w obsłudze (np „Ace of Spaces”) i __repr__ zwracała eval -friendly reprezentacji.

W rzeczywistości dokumentacja Pythona dla repr() zauważa dokładnie to:

W przypadku wielu typów funkcja ta próbuje zwrócić ciąg, który dałby obiekt o tej samej wartości po przekazaniu do eval (), w przeciwnym razie reprezentacja jest ciągiem zamkniętym w nawiasach kątowych, który zawiera nazwę typu obiektu razem z dodatkowymi informacjami, w tym często nazwą i adresem obiektu.

Oznacza to, że __str__ może zostać zaimplementowany w celu zwrócenia czegoś takiego jak „Ace of Spaces”, jak pokazano wcześniej, __repr__ może zostać zaimplementowany w celu zwrócenia Card('Spades', 1)

Ten ciąg może zostać przekazany bezpośrednio z powrotem do eval w pewnym eval „w obie strony”:

object -> string -> object

Przykładem wdrożenia takiej metody może być:

def __repr__(self):
    return "Card(%s, %d)" % (self.suit, self.pips)

Notatki

[1] Ten wynik dotyczy konkretnej implementacji. Wyświetlany ciąg pochodzi z cpython.

[2] Być może widziałeś już wynik tego podziału str() / repr() i nie znasz go. Kiedy ciągi zawierające znaki specjalne, takie jak ukośniki odwrotne, są konwertowane na ciągi za pomocą str() ukośniki odwrotne pojawiają się w stanie niezmienionym (pojawiają się raz). Kiedy są konwertowane na ciągi znaków za pomocą repr() (na przykład jako elementy wyświetlanej listy), odwrotne ukośniki są usuwane i dlatego pojawiają się dwukrotnie.

Motywacja

Właśnie stworzyłeś swoją pierwszą klasę w Pythonie, schludną, małą klasę, która zawiera w sobie kartę do gry:

class Card:
    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

W innym miejscu kodu tworzysz kilka instancji tej klasy:

ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs',  4)
six_of_hearts = Card('Hearts', 6)

Utworzyłeś nawet listę kart, aby reprezentować „rękę”:

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]

Teraz podczas debugowania chcesz zobaczyć, jak wygląda Twoja ręka, więc rób to, co przychodzi naturalnie i pisz:

print(my_hand)

Ale to, co dostajesz, to garść bełkotu:

[<__main__.Card instance at 0x0000000002533788>, 
 <__main__.Card instance at 0x00000000025B95C8>, 
 <__main__.Card instance at 0x00000000025FF508>]

Mylić, próbujesz wydrukować tylko jedną kartę:

print(ace_of_spades)

I znowu otrzymujesz ten dziwny wynik:

<__main__.Card instance at 0x0000000002533788>

Nie bać się. Zaraz to naprawimy.

Najpierw jednak ważne jest, aby zrozumieć, co się tutaj dzieje. Kiedy print(ace_of_spades) , powiedziałeś Pythonowi, że chcesz wydrukować informacje o instancji Card twój kod nazywa ace_of_spades . I szczerze mówiąc, tak się stało.

Że produkcja składa się z dwóch ważnych bitów: do type obiektu i obiektu id . Sama druga część (liczba szesnastkowa) wystarczy, aby jednoznacznie zidentyfikować obiekt w momencie wywołania print . [1]

To, co naprawdę się wydarzyło, polegało na tym, że poprosiłeś Pythona, aby „wyraził słowami” esencję tego obiektu, a następnie pokazał ci go. Bardziej jednoznaczna wersja tej samej maszyny może być:

string_of_card = str(ace_of_spades)
print(string_of_card)

W pierwszym wierszu próbujesz przekształcić instancję Card w ciąg, aw drugiej ją wyświetlić.

Problem

Problem, który napotykasz, wynika z faktu, że chociaż powiedziałeś Pythonowi wszystko, co trzeba wiedzieć o klasie Card , aby tworzyć karty, nie powiedziałeś, jak chcesz, aby instancje Card były konwertowane na łańcuchy.

A ponieważ nie wiedział, kiedy (domyślnie) napisałeś str(ace_of_spades) , dał ci to, co widziałeś, ogólną reprezentację instancji Card .

Rozwiązanie (część 1)

Ale możemy powiedzieć Pythonowi, jak chcemy, aby instancje naszych klas niestandardowych były konwertowane na ciągi. Sposób, w jaki to robimy, to __str__ „dunder” (dla podwójnego podkreślenia) lub metoda „magiczna”.

Ilekroć powiesz Pythonowi, aby utworzył ciąg z instancji klasy, będzie szukał metody __str__ w klasie i __str__ ją.

Rozważ następującą, zaktualizowaną wersję naszej klasy 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)

Tutaj zdefiniowaliśmy teraz metodę __str__ w naszej klasie Card która po prostym wyszukiwaniu słownika dla kart twarzy zwraca ciąg sformatowany w __str__ sposób.

(Zauważ, że „zwroty” są tutaj pogrubione, aby podkreślić znaczenie zwracania łańcucha, a nie tylko jego drukowania. Drukowanie może wydawać się działać, ale wtedy str(ace_of_spades) kartę, gdy zrobisz coś w stylu str(ace_of_spades) , nawet bez wywoływania funkcji drukowania w głównym programie. Aby to wyjaśnić, upewnij się, że __str__ zwraca ciąg znaków.).

Metoda __str__ jest metodą, więc pierwszy argument będzie self i nie powinien akceptować ani przekazywać argumentów dodatkowych.

Wracając do naszego problemu z wyświetlaniem karty w bardziej przyjazny dla użytkownika sposób, jeśli ponownie uruchomimy:

ace_of_spades = Card('Spades', 1)
print(ace_of_spades)

Przekonamy się, że nasza wydajność jest znacznie lepsza:

Ace of Spades

Tak wspaniale, skończyliśmy, prawda?

Cóż, aby zabezpieczyć nasze bazy, sprawdźmy dokładnie, czy rozwiązaliśmy pierwszy napotkany problem, drukując listę instancji Card , hand .

Ponownie sprawdzamy następujący kod:

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)

Ku naszemu zdziwieniu znów otrzymujemy te śmieszne kody szesnastkowe:

[<__main__.Card instance at 0x00000000026F95C8>, 
 <__main__.Card instance at 0x000000000273F4C8>, 
 <__main__.Card instance at 0x0000000002732E08>]

Co się dzieje? Powiedzieliśmy Pythonowi, w jaki sposób chcemy wyświetlać nasze instancje Card , dlaczego najwyraźniej zapomniał?

Rozwiązanie (część 2)

Cóż, za kulisami maszyneria jest nieco inna, gdy Python chce uzyskać reprezentację ciągu elementów na liście. Okazuje się, że w tym celu Python nie dba o __str__ .

Zamiast tego, wygląda na inną metodę, __repr__ , a jeśli to nie znalazł, to spadnie z powrotem na „szesnastkowym rzeczy”. [2]

Więc mówisz, że muszę zrobić dwie metody, aby zrobić to samo? Jeden, gdy chcę print moją kartę samodzielnie, a drugi, gdy jest w jakimś pojemniku?

Nie, ale najpierw przyjrzyjmy się, jak wyglądałaby nasza klasa , gdybyśmy zaimplementowali metody __str__ i __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)

W tym przypadku implementacja dwóch metod __str__ i __repr__ jest dokładnie taka sama, z tym wyjątkiem, że w celu rozróżnienia dwóch metod, (S) jest dodawane do ciągów zwracanych przez __str__ a (R) jest dodawane do ciągów zwracanych przez __repr__ .

Zauważ, że podobnie jak nasza metoda __str__ , __repr__ nie przyjmuje żadnych argumentów i zwraca ciąg znaków.

Teraz możemy zobaczyć, która metoda jest odpowiedzialna za każdy przypadek:

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)

Jak już wspomniano, metoda __str__ została wywołana, gdy przekazaliśmy naszą instancję Card do print a metoda __repr__ została wywołana, gdy przekazaliśmy listę naszych instancji do print .

W tym miejscu warto zauważyć, że tak jak możemy jawnie utworzyć ciąg z niestandardowej instancji klasy za pomocą str() jak to zrobiliśmy wcześniej, możemy również jawnie utworzyć ciąg znaków naszej klasy za pomocą wbudowanej funkcji o nazwie repr() .

Na przykład:

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)

Dodatkowo, jeśli zdefiniowane, moglibyśmy wywołać metody bezpośrednio (choć wydaje się to nieco niejasne i niepotrzebne):

print(four_of_clubs.__str__())     # 4 of Clubs (S)

print(four_of_clubs.__repr__())    # 4 of Clubs (R)

Informacje o tych zduplikowanych funkcjach ...

Programiści Pythona zdali sobie sprawę, że w przypadku, gdy chcesz zwrócić identyczne ciągi znaków z str() i repr() , być może będziesz musiał funkcjonalnie powielić metody - coś, czego nikt nie lubi.

Zamiast tego istnieje mechanizm eliminujący potrzebę tego. Tę, którą przerzuciłem cię do tego momentu. Okazuje się, że jeśli klasa implementuje metodę __repr__ , ale nie metodę __str__ , i przekażesz instancję tej klasy do str() (pośrednio lub jawnie), Python __repr__ implementacji __repr__ i użyje jej.

Dla jasności rozważ następującą wersję klasy 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)

Uwaga: ta wersja implementuje tylko metodę __repr__ . Niemniej jednak wywołania str() dają wersję przyjazną dla użytkownika:

print(six_of_hearts)            # 6 of Hearts  (implicit conversion)
print(str(six_of_hearts))       # 6 of Hearts  (explicit conversion)

podobnie jak wywołania repr() :

print([six_of_hearts])          #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts))      # 6 of Hearts  (explicit conversion)

streszczenie

Aby umożliwić instancjom klasy „pokazywanie się” w sposób przyjazny dla użytkownika, warto rozważyć wdrożenie przynajmniej metody __repr__ klasy. Jeśli pamięć służy, podczas rozmowy Raymond Hettinger powiedział, że zapewnienie implementacji __repr__ klas jest jedną z pierwszych rzeczy, których szuka podczas przeglądu kodu w Pythonie, i do tej pory powinno być jasne, dlaczego. Ilość informacji, które mogłeś dodać do instrukcji debugowania, raportów o awariach lub plików dziennika za pomocą prostej metody, jest przytłaczająca w porównaniu z marnym i często mniej niż pomocnym (typ, id) informacjami, które są podawane domyślnie.

Jeśli chcesz mieć różne reprezentacje np. W kontenerze, __str__ metody __repr__ i __str__ . (Więcej informacji o tym, jak możesz używać tych dwóch metod w różny sposób poniżej).

Zaimplementowano obie metody, styl 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)


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow