Подтвердить что ты не робот

Абстрактный атрибут (не свойство)?

Какая лучшая практика для определения атрибута абстрактного экземпляра, но не как свойство?

Я хотел бы написать что-то вроде:

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 может быть правильным, но я не уверен. Он также работает только с атрибутами класса, а не с атрибутами экземпляра.

4b9b3361

Ответ 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())