Абстрактный атрибут (не свойство)?
Какая лучшая практика для определения атрибута абстрактного экземпляра, но не как свойство?
Я хотел бы написать что-то вроде:
class AbstractFoo(metaclass=ABCMeta):
@property
@abstractmethod
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
Вместо:
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
Свойства удобны, но для простого атрибута, не требующего вычисления, они являются излишним. Это особенно важно для абстрактных классов, которые будут подклассифицированы и реализованы пользователем (я не хочу заставить кого-то использовать @property
, когда он просто мог написать self.foo = foo
в __init__
).
Абстрактные атрибуты в Python задает вопрос только как использовать @property
и @abstractmethod
: он не отвечает на мой вопрос.
Рецепт ActiveState для абстрактного атрибута класса через AbstractAttribute
может быть правильным, но я не уверен. Он также работает только с атрибутами класса, а не с атрибутами экземпляра.
Ответы
Ответ 1
Если вы действительно хотите обеспечить, чтобы подкласс определял данный атрибут, вы можете использовать метакласс. Лично я думаю, что это может быть излишним и не очень питоническим, но вы можете сделать что-то вроде этого:
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
В основном метакласс переопределяет __call__
, чтобы убедиться, что check_bar
вызывается после инициализации экземпляра.
GoodFoo() # ok
BadFoo () # yield NotImplementedError
Ответ 2
В 2018 году мы заслуживаем немного лучшего решения:
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
Это будет вести себя так:
Foo() # ok
BadFoo() # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar
В этом ответе используется тот же подход, что и в принятом ответе, но он хорошо интегрируется со встроенной ABC и не требует шаблонов помощников check_bar()
.
Вот контент 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
Приятно то, что вы можете сделать:
class AbstractFoo(metaclass=ABCMeta):
bar = abstract_attribute()
и это будет работать так же, как указано выше.
Также можно использовать:
class ABC(ABCMeta):
pass
определить пользовательский помощник ABC. PS. Я считаю этот код CC0.
Это можно улучшить, используя анализатор AST для более раннего повышения (при объявлении класса) путем сканирования кода __init__
, но пока это кажется излишним (если кто-то не захочет это реализовать).
Ответ 3
То, что вы определяете его как abstractproperty
в абстрактном базовом классе, не означает, что вам нужно создать свойство для подкласса.
например Вы можете:
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>
Если вы хотите инициализировать значение в __init__
, вы можете сделать это:
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>
Поскольку Python 3.3 abstractproperty
устарел,. Поэтому пользователи Python 3 должны использовать следующее:
from abc import ABCMeta, abstractmethod
class X(metaclass=ABCMeta):
@property
@abstractmethod
def required(self):
raise NotImplementedError
Ответ 4
Проблема не в том, но когда:
from abc import ABCMeta, abstractmethod
class AbstractFoo(metaclass=ABCMeta):
@abstractmethod
def bar():
pass
class Foo(AbstractFoo):
bar = object()
isinstance(Foo(), AbstractFoo)
#>>> True
Не имеет значения, что bar
не метод! Проблема в том, что __subclasshook__
, метод выполнения проверки, является classmethod
, поэтому заботится только о классе, а не о экземпляре strong > , имеет атрибут.
Я предлагаю вам просто не навязывать это, поскольку это трудная проблема. Альтернатива заставляет их предопределять атрибут, но это просто выходит из фиктивных атрибутов, которые просто заглушают ошибки.
Ответ 5
Я искал для этого некоторое время, но не видел ничего, что мне нравилось. Как вы знаете, знаете ли вы:
class AbstractFoo(object):
@property
def bar(self):
raise NotImplementedError(
"Subclasses of AbstractFoo must set an instance attribute "
"self._bar in it __init__ method")
class Foo(AbstractFoo):
def __init__(self):
self.bar = "bar"
f = Foo()
Вы получаете AttributeError: can't set attribute
, который раздражает.
Чтобы обойти это, вы можете:
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 __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
Это позволяет избежать AttributeError: can't set attribute
, но если вы просто оставите абстрактное свойство все вместе:
class AbstractFoo(object):
pass
class Foo(AbstractFoo):
pass
f = Foo()
f.bar
Вы получаете AttributeError: 'Foo' object has no attribute 'bar'
, который, возможно, почти так же хорош, как NotImplementedError. Так что действительно мое решение просто торгует одним сообщением об ошибке другого. И вы должны использовать self._bar, а не self.bar в init.
Ответ 6
Следуя https://docs.python.org/2/library/abc.html, вы можете сделать что-то подобное в 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())