When there's a collection involved, we need to format each individual item in the collection, as well as the overall container for those items. The following is a simple collection with both the __str__() and __repr__() methods:
class Hand:
def __init__(self, dealer_card: Card, *cards: Card) -> None:
self.dealer_card = dealer_card
self.cards = list(cards)
def __str__(self) -> str:
return ", ".join(map(str, self.cards))
def __repr__(self) -> str:
cards_text = ', '.join(map(repr, self.cards))
return f"{self.__class__.__name__}({self.dealer_card!r}, {cards_text})"
The __str__() method has a typical recipe for applying str() to the items in the collection, as follows:
- Map str() to each item in the collection. This will create an iterator over the resulting string values.
- Use ", ".join() to merge all the item strings into a single, long string.
The __repr__() method is a similar recipe to apply repr() to the items in the collection, as follows:
- Map repr() to each item in the collection. This will create an iterator over the resulting string values.
- Use ", ".join() to merge all the item strings.
- Use f"{self.__class__.__name__}({self.dealer_card!r}, {cards_text})" to combine the class name, the dealer card, and the long string of item values. This format uses !r formatting to ensure that the dealer_card attribute uses the repr() conversion too.
It's essential for __str__() to use str() and for __repr__() to use repr() when building representations of complex objects. This simple consistency guarantees that results from very complex objects with multiple layers of nesting will have consistent string values.