A maioria das classes em Python deveria ser função. O teste não é “isso é um substantivo” — é se existe um estado inválido que a classe consegue impedir.
self é um nome de parâmetro, não uma palavra-chave. __init__ não cria o objeto. E um atributo de classe é compartilhado até o momento em que você atribui pela instância.
Uma classe sem __repr__ desperdiça o seu tempo desde o instante em que existe. repr é para você, str é para quem lê, e um dos dois tem um fallback sensato.
Python não precisa de getters e setters, porque @property deixa você acrescentar validação depois sem mudar uma única chamada.
A pergunta é sempre só o que o método precisa — esta instância, a classe, ou nenhum dos dois. Errar isso quebra subclasses em silêncio.
Herança dá à subclasse tudo, inclusive o que ela não deveria ter. O problema do pinguim, e o que fazer no lugar.
Todo operador e toda função embutida do Python está fazendo uma pergunta ao seu objeto. Responda e sua classe passa a se comportar como um tipo nativo.
Quatro jeitos de guardar dados estruturados em Python. A pergunta que decide não é estilo — é se um erro de digitação deve falhar alto.
Duck typing falha no ponto da chamada, muito depois do erro. ABCs e Protocols resolvem isso, em direções opostas.
__slots__ elimina o dicionário por instância. Economiza memória de verdade em escala, pega erros de digitação de graça, e é o padrão errado.
Todo operador, toda função embutida, toda sintaxe do Python é uma pergunta feita ao seu objeto. O modelo de dados é a lista de perguntas.