Python Language
Reprezentacje łańcuchowe instancji klas: metody __str__ i __repr__
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)