@ A resposta de Oddthinking não está errada, mas acho que ela perde a verdadeira e prática razão pela qual o Python possui ABCs em um mundo de digitação de patos.
Os métodos abstratos são legais, mas, na minha opinião, eles realmente não preenchem nenhum caso de uso ainda não coberto pela digitação do duck. O poder real das classes base abstratas reside na maneira como elas permitem que você personalize o comportamento de isinstance
eissubclass
. ( __subclasshook__
é basicamente uma API mais amigável em cima do Python __instancecheck__
e do__subclasscheck__
hooks.) A adaptação de construções internas para trabalhar em tipos personalizados faz parte da filosofia do Python.
O código fonte do Python é exemplar. Aqui está como collections.Container
é definido na biblioteca padrão (no momento da redação):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Essa definição __subclasshook__
diz que qualquer classe com um __contains__
atributo é considerada uma subclasse de Container, mesmo que não a subclasse diretamente. Então eu posso escrever isso:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
Em outras palavras, se você implementar a interface certa, você é uma subclasse! Os ABCs fornecem uma maneira formal de definir interfaces em Python, mantendo-se fiel ao espírito da digitação de patos. Além disso, isso funciona de uma maneira que honra o Princípio Aberto-Fechado .
O modelo de objeto do Python parece superficialmente semelhante ao de um sistema OO mais "tradicional" (com o que quero dizer Java *) - temos classes, objetos e métodos - mas quando você arranha a superfície, encontra algo muito mais rico e mais flexível. Da mesma forma, a noção de classes abstratas de base do Python pode ser reconhecida por um desenvolvedor Java, mas na prática elas se destinam a uma finalidade muito diferente.
Às vezes, me pego escrevendo funções polimórficas que podem atuar em um único item ou em uma coleção de itens, e acho isinstance(x, collections.Iterable)
que é muito mais legível do que hasattr(x, '__iter__')
ou um try...except
bloco equivalente . (Se você não conhecia Python, qual desses três tornaria a intenção do código mais clara?)
Dito isto, acho que raramente preciso escrever meu próprio ABC e normalmente descubro a necessidade de um através da refatoração. Se eu vir uma função polimórfica fazendo muitas verificações de atributos ou muitas funções fazendo as mesmas verificações de atributos, esse cheiro sugere a existência de um ABC aguardando a extração.
* sem entrar em debate sobre se o Java é um sistema OO "tradicional" ...
Adenda : Mesmo que uma classe base abstrata pode substituir o comportamento de isinstance
e issubclass
, ainda não entra no MRO da subclasse virtual. Essa é uma armadilha em potencial para os clientes: nem todo objeto para o qual isinstance(x, MyABC) == True
os métodos estão definidos MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Infelizmente, uma dessas armadilhas "simplesmente não faça isso" (das quais o Python tem relativamente poucos!): Evite definir ABCs com __subclasshook__
métodos a e não abstratos. Além disso, você deve tornar sua definição __subclasshook__
consistente com o conjunto de métodos abstratos definidos pelo ABC.
__contains__
e uma classe que herdacollections.Container
? No seu exemplo, no Python sempre havia um entendimento compartilhado__str__
. Implementar__str__
faz as mesmas promessas que herdar de algum ABC e depois implementá-lo__str__
. Nos dois casos, você pode quebrar o contrato; não há semânticas prováveis, como as que temos na digitação estática.