Versão curta
Os ABCs oferecem um nível mais alto de contrato semântico entre clientes e as classes implementadas.
Versão longa
Existe um contrato entre uma classe e seus interlocutores. A turma promete fazer certas coisas e ter certas propriedades.
Existem diferentes níveis no contrato.
Em um nível muito baixo, o contrato pode incluir o nome de um método ou o número de parâmetros.
Em uma linguagem de tipo estático, esse contrato seria realmente aplicado pelo compilador. No Python, você pode usar o EAFP ou digitar introspecção para confirmar se o objeto desconhecido atende a esse contrato esperado.
Mas também existem promessas semânticas de nível superior no contrato.
Por exemplo, se houver um __str__()
método, espera-se retornar uma representação de seqüência de caracteres do objeto. Ele pode excluir todo o conteúdo do objeto, confirmar a transação e cuspir uma página em branco para fora da impressora ... mas existe um entendimento comum do que ele deve fazer, descrito no manual do Python.
Esse é um caso especial, onde o contrato semântico é descrito no manual. O que o print()
método deve fazer? Ele deve gravar o objeto em uma impressora ou uma linha na tela, ou algo mais? Depende - você precisa ler os comentários para entender o contrato completo aqui. Uma parte do código do cliente que simplesmente verifica se o print()
método existe confirmou parte do contrato - que uma chamada de método pode ser feita, mas não que haja acordo sobre a semântica de nível superior da chamada.
Definir uma classe base abstrata (ABC) é uma maneira de produzir um contrato entre os implementadores de classe e os chamadores. Não é apenas uma lista de nomes de métodos, mas um entendimento compartilhado do que esses métodos devem fazer. Se você herdar deste ABC, promete seguir todas as regras descritas nos comentários, incluindo a semântica do print()
método.
A digitação de pato do Python tem muitas vantagens em flexibilidade sobre a digitação estática, mas não resolve todos os problemas. Os ABCs oferecem uma solução intermediária entre a forma livre do Python e a escravidão e a disciplina de uma linguagem estática.
__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.collections.Container
é um caso degenerado, que inclui\_\_contains\_\_
apenas e significa apenas a convenção predefinida. Usar um ABC não agrega muito valor por si só, eu concordo. Eu suspeito que foi adicionado para permitir (por exemplo)Set
herdar dele. Quando você chegaSet
, de repente pertencer ao ABC tem uma semântica considerável. Um item não pode pertencer à coleção duas vezes. Isso NÃO é detectável pela existência de métodos.Set
é um exemplo melhor do queprint()
. Eu estava tentando encontrar um nome de método cujo significado fosse ambíguo e não pudesse ser grupado apenas pelo nome, então você não podia ter certeza de que ele faria a coisa certa apenas pelo nome e pelo manual do Python.Set
o exemplo em vez deprint
?Set
faz muito sentido, @Oddthinking.@ 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):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: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 quehasattr(x, '__iter__')
ou umtry...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
eissubclass
, ainda não entra no MRO da subclasse virtual. Essa é uma armadilha em potencial para os clientes: nem todo objeto para o qualisinstance(x, MyABC) == True
os métodos estão definidosMyABC
.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.fonte
isinstance(x, collections.Iterable)
é mais claro para mim, e eu conheço Python.C
subclasse exclua (ou estrague sem reparo) osabc_method()
herdados deMyABC
. A principal diferença é que é a superclasse que está estragando o contrato de herança, não a subclasse.Container.register(ContainAllTheThings)
o exemplo dado para funcionar?__subclasshook__
"qualquer classe que satisfaça esse predicado é considerada uma subclasse para os finsisinstance
eissubclass
verificações, independentemente de ter sido registrado no ABC e independentemente de ser uma subclasse direta ". Como eu disse na resposta, se você implementar a interface certa, você é uma subclasse!Um recurso útil dos ABCs é que, se você não implementar todos os métodos (e propriedades) necessários, ocorrerá um erro na instanciação, em vez de
AttributeError
, potencialmente muito mais tarde, quando você realmente tentar usar o método ausente.Exemplo de https://dbader.org/blog/abstract-base-classes-in-python
Edit: para incluir a sintaxe python3, obrigado @PandasRocks
fonte
Isso determinará se um objeto suporta um determinado protocolo sem ter que verificar a presença de todos os métodos no protocolo ou sem desencadear uma exceção no território "inimigo" devido ao não suporte muito mais fácil.
fonte
Método abstrato verifique se qualquer método que você está chamando na classe pai deve aparecer na classe filho. Abaixo estão uma maneira normal de chamar e usar o resumo. O programa escrito em python3
Maneira normal de ligar
Com método abstrato
Como methodtwo não é chamado na classe filho, obtemos erro. A implementação adequada está abaixo
fonte