Atributo abstracto (no propiedad)?

¿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 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.

    Si realmente desea imponer que una subclase define un atributo dado, puede usar metaclase. Personalmente, creo que puede ser excesivo y no muy pythonico, 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 

    Solo porque lo defina como una propiedad abstractproperty en la clase base abstracta no significa que tenga que hacer una propiedad en la subclase.

    por ejemplo, usted puede:

     In [1]: from abc import ABCMeta, abstractproperty In [2]: class X(metaclass=ABCMeta): ...: @abstractproperty ...: def required(self): ...: raise NotImplementedError ...: In [3]: class Y(X): ...: required = True ...: In [4]: Y() Out[4]: <__main__.Y at 0x10ae0d390> 

    Si desea inicializar el valor en __init__ puede hacer esto:

     In [5]: class Z(X): ...: required = None ...: def __init__(self, value): ...: self.required = value ...: In [6]: Z(value=3) Out[6]: <__main__.Z at 0x10ae15a20> 

    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 para realizar la comprobación, es un classmethod , por lo que solo le importa si la clase , no la instancia , tiene el atributo.


    Te sugiero que simplemente 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 mediante el uso del analizador AST para generar antes (en la statement de clase) escaneando el código __init__ , pero parece ser una exageración por ahora (a menos que alguien esté dispuesto a implementarlo).

    He buscado esto por un tiempo pero no he visto nada que me guste. Como probablemente sepas si lo haces:

     class AbstractFoo(object): @property def bar(self): raise NotImplementedError( "Subclasses of AbstractFoo must set an instance attribute " "self._bar in it's __init__ method") class Foo(AbstractFoo): def __init__(self): self.bar = "bar" f = Foo() 

    Obtienes un AttributeError: can't set attribute que es molesto.

    Para sortear esto puedes hacer:

     class AbstractFoo(object): @property def bar(self): try: return self._bar except AttributeError: raise NotImplementedError( "Subclasses of AbstractFoo must set an instance attribute " "self._bar in it's __init__ method") class OkFoo(AbstractFoo): def __init__(self): self._bar = 3 class BadFoo(AbstractFoo): pass a = OkFoo() b = BadFoo() print a.bar print b.bar # raises a NotImplementedError 

    Esto evita el AttributeError: can't set attribute pero si simplemente deja la propiedad abstracta al mismo tiempo:

     class AbstractFoo(object): pass class Foo(AbstractFoo): pass f = Foo() f.bar 

    Obtienes un AttributeError: 'Foo' object has no attribute 'bar' que podría decirse que es casi tan bueno como el NotImplementedError. Así que realmente mi solución es intercambiar un mensaje de error de otro … y tienes que usar self._bar en lugar de self.bar en el init .

    Siguiendo https://docs.python.org/2/library/abc.html podría 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())