---
title: "Polimorfismo em Python: O que É e Como Funciona | Python Brasil"
url: "https://python.dev.br/glossario/polimorfismo/"
markdown_url: "https://python.dev.br/glossario/polimorfismo.MD"
description: "Entenda polimorfismo em Python: duck typing, Protocol, @singledispatch, sobrecarga de operadores, ABC e padrões de design. Guia completo com exemplos."
date: "2025-10-22"
author: ""
---

# Polimorfismo em Python: O que É e Como Funciona | Python Brasil

Entenda polimorfismo em Python: duck typing, Protocol, @singledispatch, sobrecarga de operadores, ABC e padrões de design. Guia completo com exemplos.


## O que é Polimorfismo?

**Polimorfismo** significa "muitas formas" e é a capacidade de objetos diferentes **responderem à mesma mensagem** (método) de maneiras distintas. Em Python, o polimorfismo é natural graças ao **duck typing**: "se anda como um pato e faz quack como um pato, então é um pato".

Existem três formas principais de polimorfismo em Python: polimorfismo ad-hoc (sobrecarga de operadores), polimorfismo paramétrico (generics/type hints) e polimorfismo de subtipo (herança e Protocols).

## Polimorfismo por herança

A forma mais clássica: subclasses sobrescrevem métodos da classe base para fornecer comportamentos específicos.

```python
from abc import ABC, abstractmethod

class Forma(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

    def descricao(self):
        return f"{self.__class__.__name__} com área {self.area():.2f}"

class Circulo(Forma):
    def __init__(self, raio):
        self.raio = raio

    def area(self):
        return 3.14159 * self.raio ** 2

class Retangulo(Forma):
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura

    def area(self):
        return self.largura * self.altura

class Triangulo(Forma):
    def __init__(self, base, altura):
        self.base = base
        self.altura = altura

    def area(self):
        return (self.base * self.altura) / 2

# Polimorfismo em ação — mesmo código, comportamentos diferentes
formas = [Circulo(5), Retangulo(4, 6), Triangulo(3, 8)]
for forma in formas:
    print(forma.descricao())
# Circulo com área 78.54
# Retangulo com área 24.00
# Triangulo com área 12.00

# Função genérica que funciona com qualquer Forma
def area_total(formas):
    return sum(f.area() for f in formas)

print(area_total(formas))  # 114.54
```

## Duck Typing: polimorfismo sem herança

Em Python, você não precisa de uma classe base comum para ter polimorfismo. Basta que os objetos tenham os métodos esperados — isso é chamado de duck typing.

```python
class Arquivo:
    def ler(self):
        return "Lendo arquivo do disco"

class BancoDeDados:
    def ler(self):
        return "Lendo do banco de dados"

class API:
    def ler(self):
        return "Lendo da API externa"

class Cache:
    def ler(self):
        return "Lendo do cache em memória"

# Função polimórfica — funciona com qualquer objeto que tenha 'ler'
def processar_dados(fonte):
    dados = fonte.ler()
    print(f"Processando: {dados}")

for fonte in [Arquivo(), BancoDeDados(), API(), Cache()]:
    processar_dados(fonte)
```

## Protocol: subtyping estrutural (PEP 544)

O Python 3.8 introduziu `Protocol` do módulo `typing`, que formaliza o duck typing com suporte a verificação estática de tipos. Um `Protocol` define uma **interface estrutural** — qualquer classe que implemente os métodos necessários é compatível, sem precisar herdar explicitamente.

```python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Legivel(Protocol):
    def ler(self) -> str: ...

class Gravavel(Protocol):
    def gravar(self, dados: str) -> None: ...

class FonteDeDados(Legivel, Gravavel, Protocol):
    pass

class ArquivoCSV:
    def ler(self) -> str:
        return "dados,do,csv"

    def gravar(self, dados: str) -> None:
        print(f"Gravando CSV: {dados}")

class BancoPostgres:
    def ler(self) -> str:
        return "SELECT * FROM tabela"

    def gravar(self, dados: str) -> None:
        print(f"INSERT INTO tabela: {dados}")

def sincronizar(origem: Legivel, destino: Gravavel) -> None:
    dados = origem.ler()
    destino.gravar(dados)

# Funciona sem nenhuma herança explícita
sincronizar(ArquivoCSV(), BancoPostgres())

# Com @runtime_checkable, isinstance funciona
print(isinstance(ArquivoCSV(), Legivel))   # True
print(isinstance(BancoPostgres(), Legivel)) # True
```

A diferença entre `ABC` e `Protocol`: use `ABC` quando quiser herança explícita e métodos abstratos obrigatórios verificados em tempo de execução. Use `Protocol` quando quiser compatibilidade estrutural verificada pelo type checker sem forçar herança.

## Polimorfismo ad-hoc: sobrecarga de operadores

Python permite que suas classes definam o comportamento dos operadores matemáticos, de comparação e outros via dunder methods:

```python
from functools import total_ordering

@total_ordering  # Gera __le__, __gt__, __ge__ a partir de __eq__ e __lt__
class Dinheiro:
    def __init__(self, valor, moeda="BRL"):
        self.valor = valor
        self.moeda = moeda

    def __repr__(self):
        return f"Dinheiro({self.valor:.2f}, '{self.moeda}')"

    def __str__(self):
        simbolos = {"BRL": "R$", "USD": "$", "EUR": "€"}
        simbolo = simbolos.get(self.moeda, self.moeda)
        return f"{simbolo} {self.valor:.2f}"

    def _verificar_moeda(self, outro):
        if self.moeda != outro.moeda:
            raise ValueError(f"Moedas incompatíveis: {self.moeda} e {outro.moeda}")

    def __add__(self, outro):
        self._verificar_moeda(outro)
        return Dinheiro(self.valor + outro.valor, self.moeda)

    def __sub__(self, outro):
        self._verificar_moeda(outro)
        return Dinheiro(self.valor - outro.valor, self.moeda)

    def __mul__(self, fator):
        return Dinheiro(self.valor * fator, self.moeda)

    def __eq__(self, outro):
        return self.valor == outro.valor and self.moeda == outro.moeda

    def __lt__(self, outro):
        self._verificar_moeda(outro)
        return self.valor < outro.valor

    def __hash__(self):
        return hash((self.valor, self.moeda))

preco = Dinheiro(100.0)
desconto = Dinheiro(15.0)
final = preco - desconto
print(final)          # R$ 85.00
print(final * 2)      # R$ 170.00
print(preco > desconto)  # True

# Funciona em sorted(), min(), max() graças ao @total_ordering
precos = [Dinheiro(50), Dinheiro(30), Dinheiro(80)]
print(min(precos))    # R$ 30.00
```

## @singledispatch: sobrecarga de funções

`functools.singledispatch` permite criar funções com comportamentos diferentes para cada tipo de argumento — polimorfismo em funções:

```python
from functools import singledispatch

@singledispatch
def processar(dado):
    raise TypeError(f"Tipo não suportado: {type(dado)}")

@processar.register(str)
def _(dado):
    return f"String processada: {dado.upper()}"

@processar.register(int)
@processar.register(float)
def _(dado):
    return f"Número processado: {dado * 2}"

@processar.register(list)
def _(dado):
    return f"Lista processada: {len(dado)} elementos"

@processar.register(dict)
def _(dado):
    return f"Dicionário processado: {list(dado.keys())}"

print(processar("hello"))        # String processada: HELLO
print(processar(42))             # Número processado: 84
print(processar([1, 2, 3]))      # Lista processada: 3 elementos
print(processar({"a": 1}))      # Dicionário processado: ['a']
```

## Padrões de design que usam polimorfismo

### Strategy Pattern

```python
from abc import ABC, abstractmethod

class EstrategiaDesconto(ABC):
    @abstractmethod
    def calcular(self, preco: float) -> float:
        pass

class SemDesconto(EstrategiaDesconto):
    def calcular(self, preco):
        return preco

class DescontoPercentual(EstrategiaDesconto):
    def __init__(self, percentual):
        self.percentual = percentual

    def calcular(self, preco):
        return preco * (1 - self.percentual / 100)

class DescontoFixo(EstrategiaDesconto):
    def __init__(self, valor):
        self.valor = valor

    def calcular(self, preco):
        return max(0, preco - self.valor)

class Pedido:
    def __init__(self, preco, estrategia: EstrategiaDesconto):
        self.preco = preco
        self.estrategia = estrategia

    def total(self):
        return self.estrategia.calcular(self.preco)

pedidos = [
    Pedido(100, SemDesconto()),
    Pedido(100, DescontoPercentual(20)),
    Pedido(100, DescontoFixo(15)),
]

for p in pedidos:
    print(f"Total: R$ {p.total():.2f}")
# Total: R$ 100.00
# Total: R$ 80.00
# Total: R$ 85.00
```

### Command Pattern

```python
class Comando(Protocol):
    def executar(self) -> None: ...
    def desfazer(self) -> None: ...

class AdicionarItem:
    def __init__(self, lista, item):
        self.lista = lista
        self.item = item

    def executar(self):
        self.lista.append(self.item)

    def desfazer(self):
        self.lista.remove(self.item)

class RemoverItem:
    def __init__(self, lista, item):
        self.lista = lista
        self.item = item
        self._backup = None

    def executar(self):
        if self.item in self.lista:
            self._backup = self.item
            self.lista.remove(self.item)

    def desfazer(self):
        if self._backup:
            self.lista.append(self._backup)

historico = []
lista = [1, 2, 3]

cmd1 = AdicionarItem(lista, 4)
cmd1.executar()
historico.append(cmd1)

cmd2 = RemoverItem(lista, 2)
cmd2.executar()
historico.append(cmd2)

print(lista)  # [1, 3, 4]

# Desfazer todas as operações
for cmd in reversed(historico):
    cmd.desfazer()

print(lista)  # [1, 2, 3]
```

## ABC vs Protocol: quando usar cada um?

Use `ABC` quando: a hierarquia de herança é importante semanticamente; você quer garantir que subclasses implementem métodos específicos em tempo de criação; há lógica compartilhada na classe base.

Use `Protocol` quando: você quer compatibilidade com código que não herda de sua classe; está trabalhando com duck typing e quer type checking estático; quer interfaces menores e mais composáveis.

## Erros comuns

Verificar tipos manualmente com `isinstance` ou `type()` dentro de funções polimórficas geralmente é um sinal de que o polimorfismo não está sendo usado corretamente. Se você escreve `if isinstance(obj, ClasseA): ... elif isinstance(obj, ClasseB): ...`, considere mover esse comportamento para métodos das classes.

## Por que polimorfismo importa?

O polimorfismo permite escrever código **genérico e flexível** que funciona com diferentes tipos de objetos sem verificar o tipo de cada um. Isso torna o código mais **extensível** — você pode adicionar novos tipos sem alterar o código existente, seguindo o **Princípio Aberto/Fechado** (Open/Closed Principle). O resultado é código mais fácil de testar, manter e evoluir.

## Termos Relacionados

- [Classe](/glossario/classe/) - Base para criação de tipos
- [Herança](/glossario/heranca/) - Uma forma de implementar polimorfismo
- [Encapsulamento](/glossario/encapsulamento/) - Outro pilar da POO
