动机

所以你刚刚用 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__ 方法。 (有关如何在下面以不同方式使用这两种方法的更多信息)。