Na discussão sobre herança e mixins foram criadas várias classes, como Autenticavel e AutenticavelComRegistro que adicionam funcionalidades a outras classes e implementavam tudo o que precisavam para seu funcionamento. Entretanto podem existir casos em que não seja possível implementar todas as funções na própria classe, deixando com que as classes que a estende implemente essas funções. Uma forma de fazer isso é través da Orientação a objetos das ABC (abstract base classes, ou classes base abstratas).
Sem uso de classes base abstratas na orientação a objetos
Um exemplo de classe que não é possível implementar todas as funcionalidades foi dada no texto Encapsulamento da lógica do algoritmo, que discutia a leitura de valores do teclado até que um valor válido fosse lido (ou que repete a leitura caso um valor inválido tivesse sido informado). Nesse caso a classe ValidaInput implementava a lógica base de funcionamento, porém eram suas classes filhas (ValidaNomeInput e ValidaNotaInput) que implementavam as funções para tratar o que foi lido do teclado e verificar se é um valor válido ou não.
mensagem_valor_invalido = ‘Valor inválido!’def ler_entrada(self, prompt):
return input(prompt)def transformar_entrada(self, entrada):
raise NotImplementedError
def validar_valor(self, valor):
raise NotImplementedError
def __call__(self, prompt):
while True:
try:
valor = self.transformar_entrada(self.ler_entrada(prompt))
if self.validar_valor(valor):
break
except ValueError:
…
print(self.mensagem_valor_invalido)
return valor
class ValidaNomeInput(ValidaInput):
mensagem_valor_invalido = ‘Nome inválido!’
def transformar_entrada(self, entrada):
return entrada.strip().title()
def validar_valor(self, valor):
return valor != ”
class ValidaNotaInput(ValidaInput):
mensagem_valor_invalido = ‘Nota inválida!’
def transformar_entrada(self, entrada):
return float(entrada)
def validar_valor(self, valor):
return 0 <= valor <= 10
Entretanto, esse código permite a criação de objetos da classe ValidaInput mesmo sem ter uma implementação das funções transformar_entrada e validar_valor. E a única mensagem de erro ocorreria ao tentar executar essas funções, o que poderia estar longe do problema real, que é a criação de um objeto a partir de uma classe que não prove todas as implementações das suas funções, o que seria semelhante a uma classe abstrata em outras linguagens.
obj = ValidaInput()
# Diversas linhas de código
obj(‘Entrada: ‘) # Exceção NotImplementedError lançada
Com uso de classes base abstratas na orientação a objetos
Seguindo a documentação da ABC, para utilizá-las é necessário informar a metaclasse ABCMeta na criação da classe, ou simplesmente estender a classe ABC, e decorar com abstractmethod as funções que as classes que a estenderem deverão implementar. Exemplo:
from abc import ABC, abstractmethod
class ValidaInput(ABC):
mensagem_valor_invalido = ‘Valor inválido!’
def ler_entrada(self, prompt):
return input(prompt)
@abstractmethod
def transformar_entrada(self, entrada):
…
@abstractmethod
def validar_valor(self, valor):
…
def __call__(self, prompt):
while True:
try:
valor = self.transformar_entrada(self.ler_entrada(prompt))
if self.validar_valor(valor):
break
except ValueError:
…
print(self.mensagem_valor_invalido)
return valor
Desta forma, ocorrerá um erro já ao tentar criar um objeto do tipo ValidaInput, dizendo quais são as funções que precisam ser implementadas. Porém funcionará normalmente ao criar objetos a partir das classes ValidaNomeInput e ValidaNotaInput visto que elas implementam essas funções.
obj = ValidaInput() # Exceção TypeError lançada
nome_input = ValidaNomeInput() # Objeto criado
nota_input = ValidaNotaInput() # Objeto criado
Como essas funções não utilizam a referência ao objeto (self), ainda é possível decorar as funções com staticmethod, como:
from abc import ABC, abstractmethod
class ValidaInput(ABC):
mensagem_valor_invalido = ‘Valor inválido!’
@staticmethod
def ler_entrada(prompt):
return input(prompt)
@staticmethod
@abstractmethod
def transformar_entrada(entrada):
…
@staticmethod
@abstractmethod
def validar_valor(valor):
…
def __call__(self, prompt):
while True:
try:
valor = self.transformar_entrada(self.ler_entrada(prompt))
if self.validar_valor(valor):
break
except ValueError:
…
print(self.mensagem_valor_invalido)
return valor
class ValidaNomeInput(ValidaInput):
mensagem_valor_invalido = ‘Nome inválido!’
@staticmethod
def transformar_entrada(entrada):
return entrada.strip().title()
@staticmethod
def validar_valor(valor):
return valor != ”
class ValidaNotaInput(ValidaInput):
mensagem_valor_invalido = ‘Nota inválida!’
@staticmethod
def transformar_entrada(entrada):
return float(entrada)
@staticmethod
def validar_valor(valor):
return 0 <= valor <= 10
Isso também seria válido para funções decoradas com classmethod, que receberiam a referência a classe (cls).
Considerações
Não é necessário utilizar ABC para fazer o exemplo discutido, porém ao utilizar essa biblioteca ficou mais explícito quais as funções que precisavam ser implementados nas classes filhas, ainda mais que sem utilizar ABC a classe base poderia nem ter as funções, com:
mensagem_valor_invalido = ‘Valor inválido!’def ler_entrada(self, prompt):
return input(prompt)def __call__(self, prompt):
while True:
try:
valor = self.transformar_entrada(self.ler_entrada(prompt))
if self.validar_valor(valor):
break
except ValueError:
…
print(self.mensagem_valor_invalido)
return valor
Como Python possui duck-typing, não é necessário uma grande preocupação com os tipos, como definir e utilizar interfaces presentes em outras implementações de orientação a objetos, porém devido à herança múltipla, ABC pode ser utilizada como interface que não existe em Python, fazendo com que as classes implementem determinadas funções. Para mais a respeito desse assunto, recomendo as duas lives do dunossauro sobre ABC (1 e 2), e a apresentação do Luciano Ramalho sobre type hints.
Uma classe filha também não é obrigada a implementar todas as funções decoradas com abstractmethod, mas assim como a classe pai, não será possível criar objetos a partir dessa classe, apenas de uma classe filha dela que implemente as demais funções. Como se ao aplicar um abstractmethod tornasse a classe abstrata, e qualquer classe filha só deixasse de ser abstrata quando a última função decorada com abstractmethod for sobrescrita. Exemplo:
from abc import ABC, abstractmethod
class A(ABC):
@abstractmethod
def func1(self):
…
@abstractmethod
def func2(self):
…
class B(A):
def func1(self):
print(‘1’)
class C(B):
def func2(self):
print(‘2’)
a = A() # Erro por não implementar func1 e func2
b = B() # Erro por não implementar func2
c = C() # Objeto criado
Esse artigo foi publicado originalmente no meu blog, passe por lá, ou siga-me no DEV para ver mais artigos que eu escrevi.
Este artigo foi importado automaticamente por fazer parte do Planetário Dev. Quer fazer parte deste HUB de conteúdos? Faça parte do Planetário e veja as vantagens.
Não tem site ou blog? Seja um autor do site e ainda pode ser remunerado.
Mais sobre Newsletters?
32 melhores newsletters sobre negócios, startups e tecnologia para assinar em 2022
Leia também: