W3docs

Classes Base Abstratas em Python (ABC)

Aprenda como as classes base abstratas do Python (ABC) impõem uma interface comum às subclasses usando @abstractmethod, propriedades abstratas e o módulo abc.

Uma classe abstrata é uma classe que não pode ser instanciada diretamente. Ela existe apenas para definir uma interface compartilhada — um conjunto de métodos que toda subclasse concreta deve implementar. Python fornece isso por meio do módulo embutido abc (Abstract Base Classes).

Este capítulo aborda:

  • Por que as classes abstratas existem e quando usá-las
  • O módulo abcABC e abstractmethod
  • Impondo contratos de métodos com @abstractmethod
  • Propriedades abstratas
  • Métodos concretos dentro de uma classe abstrata
  • Classes abstratas vs. lançar NotImplementedError
  • Um exemplo do mundo real

Antes de ler este capítulo, certifique-se de que você está familiarizado com classes e objetos em Python e herança.

Por Que Usar Classes Abstratas?

À medida que você adiciona mais subclasses a uma hierarquia, fica fácil esquecer de implementar um método obrigatório. O problema só aparece em tempo de execução, no meio do código, o que torna a depuração dolorosa.

As classes base abstratas resolvem isso ao mover o erro para o momento em que uma classe é instanciada — não o momento em que um método ausente é chamado. Elas funcionam como um contrato: cada subclasse concreta deve implementar cada método abstrato, ou Python se recusará a criar um objeto a partir dela.

Classes abstratas vs. lançar NotImplementedError

Antes de o módulo abc existir, o padrão comum era lançar NotImplementedError em um método da classe base:

class Shape:
    def area(self):
        raise NotImplementedError("Subclasses must implement area()")

class Circle(Shape):
    pass  # forgot to implement area()

c = Circle()   # No error yet — Python lets this through
c.area()       # NotImplementedError only here, at call time

A fraqueza: Python cria o objeto sem reclamar. O erro fica invisível até que o método seja realmente chamado, o que pode ocorrer em uma parte completamente diferente do programa.

Com uma classe abstrata, o erro acontece imediatamente:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    pass  # still missing area()

c = Circle()
# TypeError: Can't instantiate abstract class Circle
#            with abstract method area

A classe está quebrada no ponto de criação — exatamente onde o contrato foi violado.

O Módulo abc

O módulo abc do Python fornece duas ferramentas que você usará constantemente:

NomeO que é
ABCUma classe base auxiliar. Herde dela para tornar sua classe abstrata.
abstractmethodUm decorador que marca um método como abstrato.

Importe-os assim:

from abc import ABC, abstractmethod

Criando uma classe abstrata

Herde de ABC e decore pelo menos um método com @abstractmethod:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

Shape agora declara que qualquer coisa que se chame de forma deve fornecer area() e perimeter(). Você não pode criar um Shape diretamente:

s = Shape()
# TypeError: Can't instantiate abstract class Shape
#            with abstract methods area, perimeter

Implementando uma Classe Abstrata

Uma classe concreta é aquela que herda da classe abstrata e implementa todos os métodos abstratos. Somente então você pode criar instâncias:

Implementando uma classe abstrata com Circle e Rectangle

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

c = Circle(5)
r = Rectangle(4, 6)

print(round(c.area(), 4))       # 78.5398
print(round(c.perimeter(), 4))  # 31.4159
print(r.area())                 # 24
print(r.perimeter())            # 20

Tanto Circle quanto Rectangle implementam todos os métodos abstratos, então Python permite que sejam instanciados.

O que acontece quando você esquece um método?

Se uma subclasse implementa apenas alguns dos métodos abstratos, ela ainda é abstrata — instanciá-la lança um TypeError que informa qual método está faltando:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class IncompleteShape(Shape):
    def area(self):
        return 0
    # perimeter() not implemented

s = IncompleteShape()
# TypeError: Can't instantiate abstract class IncompleteShape
#            with abstract method perimeter

A mensagem de erro informa exatamente qual método você esqueceu — muito mais útil do que uma falha genérica em tempo de execução.

Propriedades Abstratas

Você também pode tornar uma propriedade abstrata. Isso força as subclasses a fornecer uma propriedade (ou atributo calculado a partir de uma propriedade), não apenas um método simples. Use @property e @abstractmethod juntos, com @property no topo:

Usando propriedades abstratas

from abc import ABC, abstractmethod

class Animal(ABC):
    @property
    @abstractmethod
    def sound(self):
        pass

    def describe(self):
        return f"I make the sound: {self.sound}"

class Dog(Animal):
    @property
    def sound(self):
        return "Woof"

class Cat(Animal):
    @property
    def sound(self):
        return "Meow"

dog = Dog()
cat = Cat()

print(dog.describe())   # I make the sound: Woof
print(cat.describe())   # I make the sound: Meow

A propriedade sound em cada subclasse se comporta como um atributo somente leitura do ponto de vista do chamador, enquanto a classe abstrata garante que toda subclasse concreta forneça uma.

Métodos Concretos em uma Classe Abstrata

As classes abstratas não se limitam a métodos abstratos. Elas podem conter métodos completamente implementados (concretos) que todas as subclasses herdam automaticamente. Esta é a principal diferença entre classes abstratas e interfaces simples em outras linguagens — você pode colocar lógica compartilhada na classe abstrata:

Compartilhando lógica comum via método concreto

from abc import ABC, abstractmethod

class Logger(ABC):
    def log(self, message):
        """Concrete method — shared by all subclasses."""
        formatted = self.format_message(message)
        self.write(formatted)

    def format_message(self, message):
        return f"[LOG] {message}"

    @abstractmethod
    def write(self, message):
        """Abstract — each subclass decides how to output."""
        pass

class ConsoleLogger(Logger):
    def write(self, message):
        print(message)

class FileLogger(Logger):
    def __init__(self):
        self.entries = []

    def write(self, message):
        self.entries.append(message)

console = ConsoleLogger()
console.log("Server started")      # prints: [LOG] Server started

file_log = FileLogger()
file_log.log("Database connected")
print(file_log.entries)            # ['[LOG] Database connected']

Aqui log() e format_message() são métodos concretos compartilhados. Apenas write() — a parte que difere entre os backends — é abstrata. As subclasses nunca precisam reimplementar a lógica de formatação.

isinstance() e Classes Abstratas

Um objeto que é uma instância de uma subclasse concreta também é considerado uma instância da classe base abstrata. Isso permite escrever funções que aceitam qualquer objeto que esteja em conformidade com a interface, sem se preocupar com o tipo específico:

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

shapes = [Circle(3), Rectangle(4, 5)]

for shape in shapes:
    print(isinstance(shape, Shape))   # True for both

def total_area(shapes):
    return sum(s.area() for s in shapes)

print(round(total_area(shapes), 4))  # 48.2743

Este é o benefício central: total_area() funciona com qualquer Shape — passado, presente ou futuro — desde que o objeto implemente a interface.

Exemplo do Mundo Real: Processadores de Pagamento

As classes abstratas se destacam quando você precisa de múltiplas implementações de um mesmo conceito. Considere um sistema de pagamento que deve suportar vários provedores:

Classe abstrata para processamento de pagamentos

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def charge(self, amount):
        pass

    @abstractmethod
    def refund(self, amount):
        pass

    def process(self, amount):
        """Concrete method — shared processing logic."""
        print(f"Processing payment of ${amount}")
        self.charge(amount)

class StripeProcessor(PaymentProcessor):
    def charge(self, amount):
        print(f"Stripe: charged ${amount}")

    def refund(self, amount):
        print(f"Stripe: refunded ${amount}")

class PayPalProcessor(PaymentProcessor):
    def charge(self, amount):
        print(f"PayPal: charged ${amount}")

    def refund(self, amount):
        print(f"PayPal: refunded ${amount}")

processors = [StripeProcessor(), PayPalProcessor()]

for p in processors:
    p.process(100)
# Processing payment of $100
# Stripe: charged $100
# Processing payment of $100
# PayPal: charged $100

Adicionar um terceiro provedor de pagamento — digamos BraintreeProcessor — requer apenas implementar charge() e refund(). A lógica de process() e quaisquer verificações com isinstance continuam funcionando sem modificação.

Quando Usar Classes Abstratas

Use uma classe abstrata quando:

  • Você tem uma família de classes relacionadas que devem fornecer determinados métodos.
  • Você quer compartilhar algum código (métodos concretos) enquanto força outros métodos a serem sobrescritos.
  • Você quer que Python lance um erro imediatamente se uma subclasse estiver incompleta, em vez de aguardar uma falha em tempo de execução.

Você provavelmente não precisa de uma classe abstrata quando:

  • Você tem apenas uma ou duas subclasses e é improvável que a hierarquia cresça.
  • A interface compartilhada já é imposta por outros meios (por exemplo, dataclasses do Python ou duck typing).
  • Você apenas quer impedir a instanciação direta — uma solução mais simples é um hook __init_subclass__ ou um simples comentário.

Para conceitos relacionados, veja herança em Python e polimorfismo em Python.

Prática

Prática
What happens when you try to instantiate a class that inherits from ABC but does not implement all abstract methods?
What happens when you try to instantiate a class that inherits from ABC but does not implement all abstract methods?
Was this page helpful?