Fazer seus objetos se comportarem como tipos embutidos
Imprima um objeto simples e o Python mostra algo como <__main__.Position object at 0x7f3a>, e Position("A", 1) == Position("A", 1) é False porque, por padrão, dois objetos só são iguais se forem exatamente o mesmo objeto. Os métodos especiais (chamados dunder, pelos sublinhados duplos) permitem que sua classe se encaixe na sintaxe do Python: __repr__ controla como um objeto é mostrado, __eq__ controla ==, __add__ controla +, __lt__ controla < e assim por diante. Você nunca os chama diretamente; o Python os chama quando você usa repr(x), a == b, a + b ou sorted(items).
class Plain:
def __init__(self, cents):
self.cents = cents
a, b = Plain(500), Plain(500)
print(a == b) # False: different objects
print(a.cents == b.cents) # True
print("object at 0x" in str(a)) # True: the default display is unhelpful__repr__: mostrar o objeto com clareza
__repr__ devolve uma string que descreve o objeto para desenvolvedores. Por convenção ela se parece com o código que o construiria: Money(1999, 'USD'). Listas, o depurador e mensagens de erro a usam, o que torna os bugs muito mais legíveis. print(x) e str(x) também usam __repr__, a menos que você defina __str__ para um texto mais bonito para o usuário. Ela deve devolver a string, não imprimi-la, e {self.currency!r} numa f-string dá a forma com aspas de um atributo de texto.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
price = Money(1999)
print(price) # Money(1999, 'USD')
print([Money(5), Money(7, "EUR")]) # [Money(5, 'USD'), Money(7, 'EUR')]__eq__ e __hash__: o que significa igual?
__eq__(self, other) decide self == other. Compare os campos que definem a identidade e, quando other não é do tipo esperado, devolva o valor especial NotImplemented (não False): o Python então tenta o outro lado e por fim responde False ele mesmo, de modo que Money(1) == 5 é seguro. Definir __eq__ desativa o hash: os objetos deixam de poder ser membros de um conjunto ou chaves de dicionário, a menos que você defina também __hash__ de forma coerente, por exemplo hash((self.cents, self.currency)).
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented # let Python decide
return self.cents == other.cents and self.currency == other.currency
def __hash__(self):
return hash((self.cents, self.currency))
print(Money(500) == Money(500)) # True
print(Money(500) == Money(500, "EUR")) # False
print(Money(500) == 500) # False, no error
print(len({Money(1), Money(1)})) # 1: usable in sets nowOperadores e ordenação
__add__(self, other) dá + à sua classe. Devolva um objeto novo em vez de alterar self, e recuse o absurdo: somar dólares e euros deve gerar ValueError, somar um número solto deve devolver NotImplemented. __lt__(self, other) dá <, e isso sozinho basta para sorted(), min() e max() ordenarem seus objetos, pois só precisam perguntar «a é menor que b?». Acrescente também __le__, __gt__, __ge__, ou deixe functools.total_ordering derivá-los.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
def __add__(self, other):
if not isinstance(other, Money):
return NotImplemented
if other.currency != self.currency:
raise ValueError("cannot add different currencies")
return Money(self.cents + other.cents, self.currency)
def __lt__(self, other):
if not isinstance(other, Money):
return NotImplemented
return self.cents < other.cents
total = Money(150) + Money(275)
print(total) # Money(425, 'USD')
print(sorted([Money(30), Money(10), Money(20)])) # [Money(10, 'USD'), Money(20, 'USD'), Money(30, 'USD')]
print(sum([Money(1), Money(2)], Money(0))) # Money(3, 'USD')__repr__ deve mostrar tudo o que __eq__ examina. Não dê a + efeitos colaterais surpreendentes: ele deve devolver um valor novo e deixar os dois operandos intactos, como os números. Os testes do lab verificam estes pontos: originais inalterados, moedas diferentes recusadas e tipos sem relação dando NotImplemented.__repr__devolve uma string (comoMoney(1999, 'USD')); não imprime.__eq__compara campos e devolveNotImplementedpara tipos alheios; adicione__hash__para manter o hash.__add__devolve um objeto novo;__lt__basta parasorted,minemax.
A seguir: dataclasses, que escrevem __init__, __repr__ e __eq__ por você.
Lab: o que é avaliado
- Mostrar um objeto Money com clareza com
__repr__25 pts - Dizer quando dois valores são iguais com
__eq__25 pts - Somar dois valores com
__add__25 pts - Tornar os valores ordenáveis com
__lt__25 pts