動機

所以你剛剛用 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 你想要列印關於你的程式碼叫 ace_of_spadesCard 例項的資訊。公平地說,確實如此。

該輸出是由兩個重要的位:在 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)

在這裡,我們現在已經在 Card 類上定義了 __str__ 方法,在對面部卡進行簡單的字典查詢之後,返回一個格式化的字串,但是我們決定。

(注意返回在這裡用粗體表示,強調返回字串的重要性,而不是簡單地列印它。列印它似乎有效,但是當你做了像 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]

所以你說我必須做兩種方法來做同樣的事情?一個是因為我想把我的卡片單獨拿到另一個當它在某種容器中時?

沒有,但是首先讓我們來看看我們的類,如果我們要實現這兩個 __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)

正如所涵蓋的那樣,當我們將 Card 例項傳遞給 print 時呼叫了 __str__ 方法,當我們將例項列表傳遞 print 時呼叫了 __repr__ 方法。

在這一點上,值得指出的是,正如我們可以使用 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 程式碼審查時首先要做的事情之一,到現在為止應該清楚原因。你的資訊量可以相比微不足道的時候,往往不那麼有用(型別,ID),其預設情況下提供的資訊新增到除錯語句,崩潰報告,或日誌檔案用一個簡單的方法是壓倒性的。

如果你想要在容器內部進行不同的表示,則需要實現 __repr____str__ 方法。 (有關如何在下面以不同方式使用這兩種方法的更多資訊)。