Python 2.6, 3 абстрактное недоразумение базового класса - PullRequest
9 голосов
/ 19 мая 2010

Я не вижу того, что ожидаю, когда использую ABCMeta и abstractmethod.

Это прекрасно работает в python3:

from abc import ABCMeta, abstractmethod

class Super(metaclass=ABCMeta):
    @abstractmethod
    def method(self):
        pass

a = Super()
TypeError: Can't instantiate abstract class Super ...

А в 2.6:

class Super():
    __metaclass__ = ABCMeta
    @abstractmethod
    def method(self):
         pass

a = Super()
TypeError: Can't instantiate abstract class Super ...

Они оба также работают нормально (я получаю ожидаемое исключение), если я получаю Super из объекта, в дополнение к ABCMeta.

Они оба "потерпят неудачу" (без исключений), если я выведу Super из списка.

Я хочу, чтобы абстрактный базовый класс был списком, но абстрактным и конкретным в подклассах.

Я делаю это неправильно или я не хочу этого в python?

Ответы [ 2 ]

15 голосов
/ 19 мая 2010

С Super сборкой, как в ваших рабочих фрагментах, то, что вы вызываете, когда делаете Super():

>>> Super.__init__
<slot wrapper '__init__' of 'object' objects>

Если Super наследует от list, назовите его Superlist:

>>> Superlist.__init__
<slot wrapper '__init__' of 'list' objects>

Теперь абстрактные базовые классы предназначены для использования в качестве mixin классов, которые должны быть многократно унаследованы от (чтобы получить возможности шаблона проектирования «Шаблонный метод», которые ABCможет предложить) вместе с конкретным классом, не делая результирующий потомок абстрактным.Итак, подумайте:

>>> class Listsuper(Super, list): pass
... 
>>> Listsuper.__init__
<slot wrapper '__init__' of 'list' objects>

Видите проблему?По правилам множественного наследования вызов Listsuper() (который не не может завершиться ошибкой только из-за наличия висящего абстрактного метода) запускает тот же код, что и вызов Superlist() (который вы хотите потерпеть неудачей).Этот код на практике (list.__init__) не возражает против висячих абстрактных методов - только object.__init__.И исправление, которое, вероятно, сломало бы код, основанный на текущем поведении.

Предложенный обходной путь: если вам нужен абстрактный базовый класс, все его базы должны быть абстрактными.Таким образом, вместо того, чтобы иметь конкретные list среди ваших баз, используйте в качестве базы collections.MutableSequence, добавьте __init__, который создает атрибут ._list, и реализуйте абстрактные методы MutableSequence путем прямого делегирования self._list,Не идеально, но не так уж и больно.

2 голосов
/ 13 декабря 2014

На самом деле проблема связана с __new__, а не с __init__. Пример:

from abc import ABCMeta, abstractmethod
from collections import OrderedDict

class Foo(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        return 42

class Empty:
    def __init__(self):
        pass

class C1(Empty, Foo): pass
class C2(OrderedDict, Foo): pass

C1() завершается с ошибкой TypeError, как ожидалось, а C2.foo() возвращает 42.

>>> C1.__init__
<function Empty.__init__ at 0x7fa9a6c01400>

Как видите, он не использует object.__init__ и даже не вызывает суперкласс (object) __init__

Вы можете проверить это, позвонив __new__ самостоятельно:

C2.__new__(C2) работает просто отлично, в то время как вы получите обычный TypeError с C1.__new__(C1)

Итак, imho, он не такой четкий, как

если вы хотите абстрактный базовый класс, все его базы должны быть абстрактными.

Хотя это хорошее предложение, обратное утверждение не обязательно верно: ни OrderedDict, ни Empty не являются абстрактными, и все же подкласс первого является "конкретным", а последний "абстрактным"

Если вам интересно, я использовал OrderedDict в примере вместо list, потому что последний является «встроенным» типом, и, таким образом, вы не можете сделать:

OrderedDict.bar = lambda self: 42

И я хотел прямо заявить, что проблема не связана с ним.

...