Duck Typing e o Modelo de Dados
Padrões pythonicos não são um amontoado aleatório de truques.
Busque em todas as páginas da documentação
Padrões pythonicos não são um amontoado aleatório de truques.
Quase todas as páginas desta seção - EAFP, gerenciadores de contexto, estratégia e dispatch, injeção de dependência - são a mesma ideia subjacente, duck typing, aplicada a um problema recorrente diferente.
Duck typing significa que o Python se importa com o que um objeto pode fazer, não com a classe que ele afirma ser, e o modelo de dados (o sistema de métodos __dunder__) é como objetos comuns optam por esse comportamento para sintaxe embutida como with, for, + e len().
Noções Básicas de Padrões Pythonicos aborda exemplos concretos; EAFP vs LBYL, Gerenciadores de Contexto como Padrão, Os Padrões de Estratégia e Dispatch e Injeção de Dependência em Python cobrem cada uma das formas dessa ideia em profundidade.
Esta página é a camada por baixo de todas elas: por que os padrões do Python se parecem e se sentem diferentes dos padrões em uma linguagem sem duck typing.
Protocol), o padrão de estratégia.A frase duck typing vem do ditado "se anda como um pato e grasna como um pato, é um pato" - em termos de código, um objeto é tratado como utilizável para uma função no momento em que possui os métodos e atributos corretos, independentemente de sua classe ou ancestralidade real.
Isso é diferente de linguagens que verificam uma interface declarada ou uma classe base antes de permitir que um objeto seja usado em algum lugar - o Python simplesmente chama o método e descobre.
Uma breve ilustração torna o ponto concreto: qualquer objeto com um método .read() pode ser usado onde o código espera "algo semelhante a um arquivo", seja um arquivo real, um buffer na memória ou um wrapper de soquete de rede, sem a necessidade de uma classe base compartilhada.
def read_all(source) -> str:
return source.read() # funciona para File, io.StringIO, um wrapper de soquete - qualquer coisa com .read()O duck typing por si só explica a interoperabilidade ad hoc, mas o Python vai um passo além com o modelo de dados: um conjunto documentado de métodos __dunder__ que permitem que uma classe personalizada se conecte diretamente à sintaxe embutida em vez de apenas chamadas de método simples.
Defina __enter__/__exit__ e seu objeto funciona com with.
Defina __iter__/__next__ e ele funciona com for.
Defina __add__ e o operador + funciona nele.
O modelo de dados é o que transforma o duck typing de "qualquer coisa com o método certo funciona" para "qualquer coisa com o método certo funciona, e esse método pode ser acionado pela sintaxe comum em vez de uma chamada explícita".
Uma vez que você vê duck typing mais o modelo de dados como a base, a maioria dos padrões nomeados desta seção são lidos como aplicações do mesmo truque a uma forma específica de problema recorrente, em vez de técnicas independentes para memorizar separadamente.
EAFP é duck typing aplicado ao tratamento de erros: em vez de verificar antecipadamente se um objeto suporta uma operação (hasattr, isinstance), você simplesmente tenta a operação e captura a falha específica, porque o Python não exige que você prove a compatibilidade antes de tentar.
Gerenciadores de contexto são duck typing aplicados ao ciclo de vida de recursos: qualquer objeto que implemente __enter__/__exit__ pode ser usado com with, então o interpretador nunca precisa saber se é um arquivo, um lock, uma transação de banco de dados ou um monkey-patch temporário - ele só precisa que esses dois métodos existam.
Estratégia e dispatch são duck typing aplicados a comportamentos intercambiáveis: uma função simples, ou um dicionário mapeando chaves para chamáveis, satisfaz "a interface de estratégia" simplesmente sendo chamável com a assinatura correta, sem a necessidade de uma classe base Strategy em qualquer lugar.
Injeção de dependência em Python é geralmente mais leve do que em linguagens com tipos nominais pela mesma razão: um parâmetro de construtor tipado como um Protocol (ou deixado sem tipo em código mais simples) aceita qualquer coisa com a forma correta, então um fake, um stub e a implementação real são todos intercambiáveis sem uma cadeia de herança compartilhada.
Padrão de linguagem nominal Equivalente duck-typed do Python
------------------------------ -----------------------------------
interface Strategy { run(): X } qualquer chamável com a assinatura correta
class ConcreteStrategy uma função simples ou um dicionário de chamáveis
implements Strategy { ... }
context.setStrategy(new C()) context.strategy = minha_funcao
Esse diagrama é a forma recorrente em toda esta seção: onde outra linguagem precisa de uma declaração de interface explícita para tornar a substituição segura, o Python precisa apenas que o método ou a assinatura de chamada corretos existam no momento em que são usados.
É por isso também que vários padrões de design clássicos do Gang-of-Four parecem exagerados quando portados literalmente para o Python.
O propósito do padrão Strategy em uma linguagem com tipos nominais é definir uma interface para que estratégias concretas sejam intercambiáveis - mas as funções e dicionários de chamáveis do Python já fornecem comportamento intercambiável gratuitamente, então uma hierarquia completa de classes Strategy geralmente resolve um problema que o duck typing já resolveu.
O mesmo se aplica a partes de Factory e Observer: as funções de primeira classe do Python, closures e o modelo de dados absorvem muito do que esses padrões existem para contornar em linguagens sem eles.
Isso não significa que os padrões sejam inúteis - Padrões Factory e Builder e Observer e Pub/Sub cobrem usos reais e legítimos - mas explica por que uma implementação pythonica idiomática de um "padrão" geralmente parece menor e menos cerimoniosa do que a versão de livro didático.
O custo honesto do duck typing aparece na fronteira entre "flexível" e "não verificado": como a compatibilidade é verificada pelo uso em vez da declaração, um objeto incompatível pode viajar profundamente em uma pilha de chamadas antes de falhar, muitas vezes com um erro menos óbvio do que uma violação de interface em tempo de compilação produziria.
EAFP vs LBYL cobre o gerenciamento desse custo para operações individuais, e a tipagem estrutural via Protocol (abordada na seção de type hints) recupera parte da verificação antecipada que um sistema de tipos nominal oferece, sem abandonar a flexibilidade do duck typing em tempo de execução.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Duck typing (implícito) | Cerimônia zero; funciona com qualquer objeto compatível, independentemente da origem | Incompatibilidades surgem tarde, às vezes profundamente em uma pilha de chamadas | Pequenos scripts, código exploratório, colagem de objetos não relacionados |
| Modelo de dados (métodos dunder) | Integra-se com sintaxe embutida (with, for, +) que os usuários já conhecem | Fácil de implementar sutilmente errado (por exemplo, um __exit__ com vazamento) | Tipos personalizados que devem parecer embutidos |
Protocol (tipagem estrutural) | O verificador estático captura incompatibilidades de forma antes do tempo de execução | Requer type hints e um verificador no fluxo de trabalho | APIs públicas e equipes maiores que desejam a flexibilidade do duck typing com feedback mais cedo |
| ABC / herança explícita | Descoberta, imposta no momento da instanciação | Força uma relação de herança que pode não se encaixar conceitualmente | Sua própria hierarquia de classes com uma relação genuína é-um |
__enter__/__exit__ ou __iter__ é a maneira sancionada de fazer um tipo personalizado se comportar como um tipo embutido.Um objeto é utilizável para uma função com base nos métodos e atributos que ele possui no momento em que é usado, não com base em sua classe declarada ou em uma interface explícita que ele implementa.
O conjunto documentado de métodos __dunder__ do Python (__enter__, __iter__, __add__ e muitos outros) que permitem que uma classe personalizada se conecte à sintaxe embutida - with, for, operadores - em vez de exigir uma chamada de método explícita.
Eles estão relacionados, mas são distintos. Duck typing é o princípio geral de que o comportamento, não o tipo declarado, determina a usabilidade. O modelo de dados é o mecanismo específico que o Python fornece para que os objetos participem da sintaxe embutida sob esse princípio.
Porque duck typing e funções de primeira classe já resolvem parte do que esses padrões existem para contornar - uma interface explícita ou hierarquia de classes para intercambialidade é frequentemente desnecessária quando qualquer objeto ou função com a forma correta já se qualifica.
Não - Protocol dá ao duck typing uma forma estrutural e verificável: um verificador estático verifica se a forma correta existe antes do tempo de execução, sem forçar a herança. É duck typing com feedback mais cedo, não um substituto para ele.
Porque duck typing adia "isso funciona" para o momento de uso, EAFP é a contrapartida natural de tratamento de erros: tente a operação e, se o objeto realmente não a suportar, capture a exceção específica em vez de verificar antecipadamente.
with funciona em qualquer coisa que implemente __enter__ e __exit__, independentemente do que o objeto seja - um arquivo, um lock, uma transação de banco de dados - o que é duck typing aplicado especificamente ao ciclo de vida de recursos.
Sim, quando há uma relação genuína é-um que você deseja modelar e impor - herança e duck typing não são opostos, e programas Python usam ambos regularmente onde cada um se encaixa.
Um objeto incompatível não é rejeitado antecipadamente; ele pode ser passado e até parcialmente usado antes que uma chamada incompatível finalmente gere AttributeError ou TypeError, às vezes longe de onde a incompatibilidade se originou.
Porque uma função simples ou um dicionário mapeando nomes para chamáveis já fornece comportamento intercambiável - a hierarquia de classes que o padrão Strategy usa em outras linguagens existe especificamente para contornar a ausência de funções de primeira classe e duck typing lá.
CPython tem caminhos rápidos e especializados para métodos dunder comuns usados pela sintaxe embutida (como __len__ para len()), então implementá-los corretamente geralmente não é uma preocupação de desempenho em relação a uma chamada de método explícita equivalente.
Como qualquer objeto com a forma de método correta satisfaz uma dependência, injetar um fake ou stub para teste não precisa de uma classe base compartilhada com a implementação real - apenas uma interface correspondente, que o duck typing (opcionalmente verificado via Protocol) já fornece.
Versões de Stack: Esta página é conceitual e não está vinculada a uma versão específica de stack.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026