python3 create abstract class




Atributo abstracto(no propiedad)? (4)

¿Cuál es la mejor práctica para definir un atributo de instancia abstracta, pero no como una propiedad?

Me gustaría escribir algo como:

class AbstractFoo(metaclass=ABCMeta):

    @property
    @abstractmethod
    def bar(self):
        pass

class Foo(AbstractFoo):

    def __init__(self):
        self.bar = 3

En lugar de:

class Foo(AbstractFoo):

    def __init__(self):
        self._bar = 3

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def setbar(self, bar):
        self._bar = bar

    @bar.deleter
    def delbar(self):
        del self._bar

Las propiedades son útiles, pero para atributos simples que no requieren cómputo son una exageración. Esto es especialmente importante para las clases abstractas que serán subclasificadas e implementadas por el usuario (no quiero forzar a alguien a usar @property cuando podría haber escrito self.foo = foo en el __init__ ).

Los atributos abstractos en la pregunta de Python se proponen como única respuesta para usar @property y @abstractmethod : no responde a mi pregunta.

La receta de ActiveState para un atributo de clase abstracta a través de AbstractAttribute puede ser la forma correcta, pero no estoy seguro. También funciona solo con atributos de clase y no con atributos de instancia.


El problema no es qué , sino cuándo :

from abc import ABCMeta, abstractmethod

class AbstractFoo(metaclass=ABCMeta):
    @abstractmethod
    def bar():
        pass

class Foo(AbstractFoo):
    bar = object()

isinstance(Foo(), AbstractFoo)
#>>> True

¡No importa que la bar no sea un método! El problema es que __subclasshook__ , el método de verificación, es un classmethod , por lo que solo le importa si la clase , no la instancia , tiene el atributo.

Te sugiero que no lo obligues, ya que es un problema difícil. La alternativa es obligarlos a predefinir el atributo, pero eso solo deja alrededor de los atributos ficticios que simplemente silencian los errores.


Es 2018, nos merecemos una solución un poco mejor:

from better_abc import ABCMeta, abstract_attribute    # see below

class AbstractFoo(metaclass=ABCMeta):

    @abstract_attribute
    def bar(self):
        pass

class Foo(AbstractFoo):
    def __init__(self):
        self.bar = 3

class BadFoo(AbstractFoo):
    def __init__(self):
        pass

Se comportará así:

Foo()     # ok
BadFoo()  # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar

Esta respuesta utiliza el mismo enfoque que la respuesta aceptada, pero se integra bien con el ABC incorporado y no requiere check_bar() ayudantes check_bar() .

Aquí está el contenido de better_abc.py :

from abc import ABCMeta as NativeABCMeta

class DummyAttribute:
    pass

def abstract_attribute(obj=None):
    if obj is None:
        obj = DummyAttribute()
    obj.__is_abstract_attribute__ = True
    return obj


class ABCMeta(NativeABCMeta):

    def __call__(cls, *args, **kwargs):
        instance = NativeABCMeta.__call__(cls, *args, **kwargs)
        abstract_attributes = {
            name
            for name in dir(instance)
            if getattr(getattr(instance, name), '__is_abstract_attribute__', False)
        }
        if abstract_attributes:
            raise NotImplementedError(
                "Can't instantiate abstract class {} with"
                " abstract attributes: {}".format(
                    cls.__name__,
                    ', '.join(abstract_attributes)
                )
            )
        return instance

Lo bueno es que puedes hacer:

class AbstractFoo(metaclass=ABCMeta):
    bar = abstract_attribute()

y funcionará igual que el anterior.

También se puede utilizar:

class ABC(ABCMeta):
    pass

para definir ayudante ABC personalizado. PD. Considero que este código es CC0.

Esto podría mejorarse utilizando el analizador AST para generar antes (en la declaración de clase) escaneando el código __init__ , pero parece ser una exageración por ahora (a menos que alguien esté dispuesto a implementar).


Si realmente desea imponer que una subclase define un atributo dado, puede usar metaclase. Personalmente, creo que puede ser excesivo y no muy pitónico, pero se podría hacer algo como esto:

 class AbstractFooMeta(type):

     def __call__(cls, *args, **kwargs):
         """Called when you call Foo(*args, **kwargs) """
         obj = type.__call__(cls, *args, **kwargs)
         obj.check_bar()
         return obj


 class AbstractFoo(object):
     __metaclass__ = AbstractFooMeta
     bar = None

     def check_bar(self):
         if self.bar is None:
             raise NotImplementedError('Subclasses must define bar')


 class GoodFoo(AbstractFoo):
     def __init__(self):
         self.bar = 3


 class BadFoo(AbstractFoo):
     def __init__(self):
         pass

Básicamente, la meta clase redefine __call__ para asegurarse de que se check_bar después del inicio en una instancia.

GoodFoo()  # ok
BadFoo ()  # yield NotImplementedError

Siguiendo https://docs.python.org/2/library/abc.html puedes hacer algo como esto en Python 2.7:

from abc import ABCMeta, abstractproperty


class Test(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def test(self): yield None

    def get_test(self):
        return self.test


class TestChild(Test):

    test = None

    def __init__(self, var):
        self.test = var


a = TestChild('test')
print(a.get_test())




abstract