passar - sobrescrever metodo python




Por que usar classes base abstratas em Python? (4)

Versão curta

ABCs oferecem um nível mais alto de contrato semântico entre os clientes e as classes implementadas.

Versão longa

Existe um contrato entre uma classe e seus chamadores. A classe promete fazer certas coisas e ter certas propriedades.

Existem diferentes níveis para o contrato.

Em um nível muito baixo, o contrato pode incluir o nome de um método ou seu número de parâmetros.

Em uma linguagem com tipagem estática, esse contrato seria realmente imposto pelo compilador. No Python, você pode usar o EAFP ou a introspecção para confirmar que o objeto desconhecido atende a esse contrato esperado.

Mas também há promessas semânticas de nível superior no contrato.

Por exemplo, se houver um __str__() , espera-se que ele retorne uma representação de string do objeto. Ele pode excluir todo o conteúdo do objeto, confirmar a transação e cuspir uma página em branco fora da impressora ... mas há um entendimento comum do que ela deve fazer, descrita no manual do Python.

Esse é um caso especial, onde o contrato semântico é descrito no manual. O que o método print() fazer? Deve escrever o objeto para uma impressora ou uma linha para a tela ou outra coisa? 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 método print() existe confirmou parte do contrato - que uma chamada de método pode ser feita, mas não que haja concordância na 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 uma compreensão compartilhada do que esses métodos devem fazer. Se você herdar deste ABC, estará prometendo seguir todas as regras descritas nos comentários, incluindo a semântica do método print() .

A tipificação de patos do Python tem muitas vantagens em flexibilidade sobre a tipagem 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 disciplina de uma linguagem tipada de forma estática.

Como estou acostumado com as antigas maneiras de digitar pato no Python, não consigo entender a necessidade do ABC (classes base abstratas). A help é boa em como usá-los.

Eu tentei ler o raciocínio no PEP , mas passou por cima da minha cabeça. Se eu estivesse procurando por um contêiner de sequência mutável, eu verificaria __setitem__ ou, mais provavelmente, tentaria usá-lo ( EAFP ). Eu não encontrei um uso real para o módulo de numbers , que usa ABCs, mas é o mais próximo que eu tenho de entender.

Alguém pode explicar a razão para mim, por favor?


@ A resposta de Oddthinking não está errada, mas acho que falta a verdadeira razão prática que o Python tem ABCs em um mundo de tipagem de pato.

Os métodos abstratos são perfeitos, mas na minha opinião eles não preenchem realmente nenhum caso de uso que ainda não esteja coberto por tipagem de pato. O verdadeiro poder das classes básicas abstratas reside na maneira como elas permitem que você personalize o comportamento de isinstance e issubclass . ( __subclasshook__ é basicamente uma API mais amigável sobre os ganchos __subclasscheck__ e __subclasscheck__ do Python.) A adaptação de construções __subclasscheck__ para trabalhar em tipos customizados é muito parte da filosofia do Python.

O código-fonte do Python é exemplar. Here está como collections.Container é definido na biblioteca padrão (no momento da escrita):

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

Esta definição de __subclasshook__ diz que qualquer classe com um atributo __contains__ é 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! ABCs fornecem uma maneira formal de definir interfaces em Python, mantendo-se fiel ao espírito da tipagem de pato. 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" (pelo qual quero dizer Java *) - temos suas classes, seus objetos, seus métodos - mas quando você risca a superfície, você encontra algo muito mais rico e mais flexível. Da mesma forma, a noção Python de classes base abstratas pode ser reconhecida por um desenvolvedor Java, mas na prática elas são destinadas a um propósito muito diferente.

Eu às vezes me vejo escrevendo funções polimórficas que podem atuar em um único item ou uma coleção de itens, e acho que isinstance(x, collections.Iterable) é muito mais legível que hasattr(x, '__iter__') ou uma try...except equivalente try...except bloco try...except . (Se você não conhecesse o Python, qual desses três tornaria a intenção do código mais clara?)

Dito isso, acho que raramente preciso escrever meu próprio ABC e normalmente descubro a necessidade de um por meio da refatoração. Se eu vejo 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 esperando para ser extraído.

* sem entrar no debate sobre se o Java é um sistema OO "tradicional" ...

Adendo : Mesmo que uma classe base abstrata possa sobrescrever o comportamento de isinstance e issubclass , ela ainda não entra na MRO da subclasse virtual. Esta é uma armadilha em potencial para os clientes: nem todo objeto para o qual é isinstance(x, MyABC) == True tem os métodos definidos no 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, essa é uma das armadilhas "apenas não faça isso" (das quais o Python tem relativamente poucas!): Evite definir ABCs com métodos __subclasshook__ e não abstratos. Além disso, você deve fazer sua definição de __subclasshook__ consistente com o conjunto de métodos abstratos que seu ABC define.


O método abstrato garante que o método que você está chamando na classe pai tenha que aparecer na classe filha. Abaixo está a maneira noraml de chamar e usar abstrato. O programa escrito em python3

Maneira normal de chamar

class Parent:
def methodone(self):
    raise NotImplemented()

def methodtwo(self):
    raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

'methodone () é chamado'

c.methodtwo()

NotImplementedError

Com método abstrato

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

TypeError: Não é possível instanciar a classe abstrata Son com métodos abstratos methodtwo.

Como o método twotwo não é chamado na classe filha, recebemos um erro. A implementação adequada está abaixo

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

'methodone () é chamado'


Um recurso útil do ABC é que, se você não implementar todos os métodos (e propriedades) necessários, obterá um erro na instanciação, em vez de um AttributeError , possivelmente muito mais tarde, quando você realmente tentar usar o método ausente.

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

Exemplo de https://dbader.org/blog/abstract-base-classes-in-python

Edit: para incluir a sintaxe python3, obrigado @PandasRocks





abc