12.5 Interfaces

O Python não possui uma palavra reservada interface. Mesmo sem uma palavra reservada para a mesma, toda classe possui uma interface. Interfaces são os atributos públicos definidos (que em Python são tanto atributos quanto métodos) em uma classe - isso inclui os métodos especiais como str () e add () .

Uma interface vista como um conjunto de métodos para desempenhar um papel é o que os programadores da SmallTalk chamavam de protocolo, e este termo foi disseminado em comunidades de programadores de linguagens dinâmicas. Esse protocolo funciona como um contrato.

Os protocolos são independentes de herança. Uma classe pode implementar vários protocolos, como os mix-ins. Protocolos são interfaces e são definidos apenas por documentação e convenções em linguagens dinâmicas, por isso são considerados informais. Os protocolos não podem ser verificados estaticamente pelo interpretador.

O método str () , por exemplo, é esperado que retorne uma representação do objeto em forma de string . Nada impede de fazermos outras coisas dentro do método como deletar algum conteúdo ou fazer algum cálculo; ao invés de retornarmos apenas a string . Mas há um entendimento prévio comum do que este método deve fazer e está presente na documentação do Python. Este é um exemplo onde o contrato semântico é descrito em um manual. Algumas linguagens de tipagem estática, como Java, possuem interfaces em sua biblioteca padrão e podem garantir este contrato em tempo de compilação.

A partir do Python 2.6, a definição de interfaces utilizando o módulo ABC é uma solução mais elegante do que os mix-ins. Nossa classe Autenticavel pode ser uma classe abstrata com o método abstrato autentica() :

import abc
python
class Autenticavel(abc.ABC): @abc.abstractmethod
python
def autentica(self, senha): pass

Como se trata de uma interface em uma linguagem de tipagem dinâmica como o Python, a boa prática é documentar esta classe garantindo o contrato semântico:

import abc
python
class Autenticavel(abc.ABC):

"""Classe abstrata que contém operações de um objeto autenticável.

As subclasses concretas devem sobrescrever o método autentica """

@abc.abstractmethod

def autentica(self, senha):

""" Método abstrato que faz verificação da senha. Devolve True se a senha confere, e False caso contrário. """

E nossas classes Gerente , Diretor e Cliente herdariam a classe Autenticavel . Mas qual a diferença de herdar muitos mix-ins e muitas ABCs? Realmente, aqui não há grande diferença e voltamos ao problema anterior dos mix-ins - muito acoplamento entre classes que gera a herança múltipla.

Mas a novidade das ABCs é seu método register() . As ABCs introduzem uma subclasse virtual, que são classes que não herdam de uma classe mas são reconhecidas pelos métodos isinstance() e issubclass() . Ou seja, nosso Gerente não precisa herdar a classe Autenticavel , basta registrarmos ele como uma implementação da classe Autenticavel .

Autenticavel.register(Gerente)

E testamos os métodos isinstance() e issubclass() com uma instância de Gerente :

gerente = Gerente(‘João’, ‘111111111-11’, 3000.0)

168 12.5 INTERFACES

print(isinstance(Autenticavel)) print(issubclass(Autenticavel))

Que vai gerar a saída:

True True

O Python não vai verificar se existe uma implementação do método autentica em Gerente quando registrarmos a classe. Ao registrarmos a classe Gerente como uma Autenticavel , prometemos ao Python que a classe implementa fielmente a nossa interface Autenticavel definida. O Python vai acreditar nisso e retornar True quando os métodos isinstance() e issubclass() forem chamados. Se mentirmos, ou seja, não implementarmos o método autentica() em Gerente , uma exceção será lançada quando tentarmos chamar este método.

Vejamos um exemplo com a classe Diretor . Não vamos implementar o método autentica() e registrar uma instância de Diretor como um Autenticavel :

class Diretor(Funcionario): # código omitido
python
if name == ' main ': Autenticavel.register(Diretor)

d = Diretor(‘José’, ‘22222222-22’, 3000.0)

d.autentica(’?‘)

E temos como saída:

Traceback (most recent call last): File <stdin>, line 47, in <module>
 
d.autentica('?')
 

AttributeError: ‘Diretor’ object has no attribute ‘autentica’

Novamente, podemos tratar a exceção ou utilizar os métodos isinstance() ou issubclass() para verificação. Apesar de considerada má práticas por muitos pythonistas, o módulo de classes abstratas justifica a utilização deste tipo de verificação. A verificação não é de tipagem, mas se um objeto está de acordo com a interface:

if name == ' main ': Autenticavel.register(Diretor)

d = Diretor(‘José’, ‘22222222-22’, 3000.0)

if (isinstance(d, Autenticavel)): d.autentica('?')
java
else:
 
java
print("Diretor não implementa a interface Autenticavel")

E portanto, nossa classe SistemaInterno ficaria assim:

from autenticavel import Autenticavel
python
java
class SistemaInterno:‌
python
def login(self, obj):
python
if (isinstance(obj, Autenticavel)): obj.autentica(obj.senha) return True
java
else:
 
java
print(f'{} não é autenticável {self. class . name }') return False

Dessa maneira fugimos da herança múltipla e garantimos um contrato, um protocolo. Classes abstratas complementam o duck typing, provendo uma maneira de definir interfaces quando técnicas como usar hasattr() são ruins ou sutilmente erradas. Você pode ler mais a respeito no documento da PEP que introduz classes abstratas - é a PEP 3119 e você pode acessar seu conteúdo neste link: https://www.python.org/dev/peps/pep-3119/

Algumas ABCs também podem prover métodos concretos, ou seja, não abstratos. Por exemplo, a classe Interator do módulo collections da biblioteca padrão do Python possui um método

iter () retornando ele mesmo. Esta ABC pode ser considerada uma classe mix-in.

O Python já vem com algumas estruturas abstratas (ver módulos collections, numbers e io).


⬅️ Capítulo Anterior | Próximo Capítulo ➡️