create - python interface
Atributo abstracto(no propiedad)? (6)
¿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).
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 .
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())
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>