O que é?
Duck typing é um estilo de codificação de linguagens dinamicamente tipadas onde o tipo de uma variável não importa, contanto que seu comportamento seja o desejado. O nome "tipagem de pato" vem da expressão "se anda como pato, nada como um pato e faz quack como um pato, então provavelmente é um pato".
Quando criamos uma função ou um método em Python o tipo do parâmetro não é a parte importante, mas o que realmente importa é se o mesmo vai possuir os métodos e atributos esperados:
def realizar_operacao(operacao, **kwargs):
operacao.realizar(**kwargs)
A função realizar_operacao é um exemplo de como duck typing é aplicado: ela recebe como parâmetro o objeto operacao e executa o método realiza sem se importar com o tipo do objeto.
É interessante notar que realizar_operacao utiliza a variável mágica **kwargs, que recebe qualquer número de parâmetros nominais após passado o objeto referente à operação, não importando quais sejam.
Essa função pode ser implementada para realizar uma transferência, como vemos a seguir:
transferencia = Transferencia()
realizar_operacao(
transferencia,
conta_origem='14725-8',
conta_destino='85296-3',
valor=10000
)
A função também pode ser implementada para realizar um depósito simples:
deposito = Deposito()
realiza_operacao(
deposito,
conta_destino='85296-3',
valor=10000
)
Melhor pedir perdão que permissão
Quando estamos trabalhando com tipagem estática, o comum é utilizar uma interface como um contrato, definindo os atributos e métodos obrigatórios, e esperar que o objeto sendo recebido seja do tipo de uma classe que implemente esta interface, garantindo que o objeto que estamos recebendo tenha o comportamento esperado.
Observe o exemplo em Java a seguir:
public interface Pagamento {
void realizar();
}
public class Deposito implements Pagamento {
@Override
public void realizar() {
System.out.println("Deposito::realizar");
}
}
public class Transferencia implements Pagamento {
@Override
public void realizar() {
System.out.println("Transferencia::realizar");
}
}
Veja que são criadas a interface de Pagamento, que define o contrato das classes que devem ter o método realizar, e duas classes que a implementam.
public class Billing {
public void realizarPagamento(Pagamento operacao) {
operacao.realizar();
}
}
Criamos a classe Billing que recebe um objeto que implementa a interface Pagamento e executa o método realizar deste objeto.
public class Main {
public static void main(String args[]) {
Billing billing = new Billing();
Deposito deposito = new Deposito();
Transferencia transferencia = new Transferencia();
billing.realizarPagamento(deposito);
billing.realizarPagamento(transferencia);
}
}
Por fim é implementado um objeto da classe Billing que realiza uma transferência e um pagamento.
Esse tipo de abordagem evita erros, como executar um método no objeto recebido que não existe. Como Python não possui suporte a interfaces, a abordagem para prevenir problemas como executar um método que não existe em um objeto segue outro tipo de prática: a EAFP.
EAFP (easier to ask for forgiveness than permissione), do inglês "Melhor pedir perdão que permissão", é um estilo de codificação que envolve assumir que os comportamentos desejados em um determinado objeto existem e qualquer problema relacionado é tratado como uma exceção, assim o código se mantém limpo, conciso e legível.
A seguir temos o exemplo prévio traduzido em Python:
class Deposito:
def realizar(self):
print("Deposito::realizar")
class Transferencia:
def realizar(self):
print("Transferencia::realizar")
Temos as classes das operações praticamente idênticas ao exemplo em Java:
class Billing:
def realizar_pagamento(self, operacao):
try:
operacao.realizar()
except AttributeError:
print('a operação não pôde ser realizada')
O método realizar_pagamento da classe Billing recebe a operação como parâmetro, porém não espera um tipo específico, mas sim executa dentro de uma declaração try/except: se o método não existir, uma exceção do tipo AttributeError será disparada e capturada pelo except, então uma mensagem será impressa informando que há um problema.
def main():
billing = Billing()
deposito = Deposito()
transferencia = Transferencia()
billing.realizar_pagamento(deposito)
billing.realizar_pagamento(transferencia)
if __name__ == "__main__":
main()
Por fim, implementamos as classes em nossa função main da mesma forma que no exemplo em Java.
Conclusão
Python é uma linguagem singular com toda uma forma de programar e uma cultura interna, o jeito "pythônico", de como escrever o código e implementar boas práticas. É interessante ressaltar que essa cultura de boas práticas agrega uma importância fundamental à legibilidade do código.
Vale citar que tanto a comunidade quanto o próprio time de desenvolvimento seguem a filosofia deixada por um de seus criadores: o Zen do Python, que pode ser acessado ao digitar o comando import this em um terminal interativo do Python:
The Zen of Python, by Tim Peters
Bonito é melhor que feio.
Explícito é melhor que implícito.
Simples é melhor que complexo.
Complexo é melhor que complicado.
Linear é melhor do que aninhado.
Disperso é melhor que denso.
Legibilidade conta.
Casos especiais não são especiais o bastante para quebrar as regras.
Ainda que praticidade vença a pureza.
Erros nunca devem passar silenciosamente.
A menos que sejam explicitamente silenciados.
Diante da ambiguidade, recuse a tentação de adivinhar.
Deveria haver um -- e de preferência só um -- modo óbvio para fazer algo.
Embora esse modo possa não ser óbvio a princípio a menos que você seja
holandês.
Agora é melhor que nunca.
Embora nunca frequentemente seja melhor que já.
Se a implementação é difícil de explicar, é uma má ideia.
Se a implementação é fácil de explicar, pode ser uma boa ideia.
Namespaces são uma grande ideia -- vamos ter mais dessas!