Duck Typing com Python

Python é uma linguagem dinamicamente tipada que não possui interfaces. Essas características da linguagem deixam claro que Python não implementa polimorfismo da mesma forma que linguagens como Java e PHP: utiliza Duck Typing.

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!

Artigos relacionados