Почему я не могу наследовать от dict AND Exception в Python?

Я получил следующий класс:

class ConstraintFailureSet(dict, Exception) :
    """
        Container for constraint failures. It act as a constraint failure itself
        but can contain other constraint failures that can be accessed with a dict syntax.
    """

    def __init__(self, **failures) :
        dict.__init__(self, failures)
        Exception.__init__(self)

print isinstance(ConstraintFailureSet(), Exception)
True
raise ConstraintFailureSet()
TypeError: exceptions must be classes, instances, or strings (deprecated), not ConstraintFailureSet

Какого черта ?

И хуже всего то, что я не могу попробовать super(), так как Exception - это старый класс...

РЕДАКТИРОВАТЬ: И да, я пытался переключить порядок наследования/инициализации.

EDIT2: я использую CPython 2.4 на Ubuntu8.10. Вы, новенькие, знаете, полезна ли такая информация ;-). Так или иначе, эта маленькая загадка закрыла рот трем моим коллегам. Ты был бы моим лучшим другом дня...


person e-satis    schedule 21.11.2008    source источник


Ответы (6)


И Exception, и dict реализованы на C.

Я думаю, вы можете проверить это следующим образом:

>>> class C(object): pass
...
>>> '__module__' in C.__dict__
True
>>> '__module__' in dict.__dict__
False
>>> '__module__' in Exception.__dict__
False

Поскольку Exception и dict имеют разные представления о том, как хранить свои данные внутри, они несовместимы, и поэтому вы не можете наследовать их обоих одновременно.

В более поздних версиях Python вы должны получить исключение в тот момент, когда попытаетесь определить класс:

>>> class foo(dict, Exception):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict
person Community    schedule 21.11.2008

Что в этом плохого?

class ConstraintFailure( Exception ):
    def __init__( self, **failures ):
        self.failures= failures # already a dict, don't need to do anything
    def __getitem__( self, key ):
        return self.failures.get(key)

Это исключение, и оно содержит другие исключения во внутреннем словаре с именем failures.

Не могли бы вы обновить свою проблему, чтобы перечислить некоторые конкретные вещи, которые это не может сделать?

try:
    raise ConstraintFailure( x=ValueError, y=Exception )
except ConstraintFailure, e:
    print e['x']
    print e['y']


<type 'exceptions.ValueError'>
<type 'exceptions.Exception'>
person S.Lott    schedule 21.11.2008
comment
Ваше решение подразумевает, что мне нужно переписать весь интерфейс словаря, используя волшебный метод (setitem, items, iteritems, len и т. д.), что означает добавление вручную дюжины методов, которые мне также придется тестировать. - person e-satis; 22.11.2008
comment
Вы действительно используете ВСЕ возможности словаря? Почему нельзя использовать e.failures для явной ссылки на прикрепленный словарь? - person S.Lott; 22.11.2008
comment
Мне понадобятся как минимум функции для перечисления/перебора ключей, значений, len и get.setitem. Но не беспокойтесь, UserDict сделает это, так как я не думаю, что этот код является узким местом... В любом случае спасибо за ваше предложение :-) - person e-satis; 24.11.2008
comment
Итак, вам нужно пять методов: ключи, значения, элементы и setitem? Я не понимаю, что вы хотите сделать и что могут охватывать десятки. Есть ли у вас какие-то другие методы или какие-то другие особенности? - person S.Lott; 24.11.2008

Не причина, а решение

На данный момент я до сих пор не знаю, почему, но я обхожу это, используя UserDict.UserDict. Это медленнее, так как это чистый Python, но я не думаю, что в этой части приложения это будет проблематично.

Все равно интересен ответ ;-)

person e-satis    schedule 21.11.2008
comment
GvR предупредил об этом в своем Унифицирующие типы и классы в Python 2.2, кстати (см., python.org/download/releases/2.2.2/descrintro, "You can use multiple inheritance, but you can't multiply inherit from different built-in types (for example, you can't create a type that inherits from both the built-in dict and list types).") - person mlvljr; 09.06.2011

Какая версия питона?

В 2.5.1 я даже не могу определить класс, наследуемый как от dict, так и от Exception:

>>> class foo(dict, Exception):
...   pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

Если вы используете более старую версию, возможно, она не выполняет эту проверку во время определения типа, и позже конфликт вызывает странности.

person Dave Costa    schedule 21.11.2008

Я почти уверен, что с 2.4 проблема вызвана исключениями из классов старого стиля.

$ python2.4
Python 2.4.4 (#1, Feb 19 2009, 09:13:34)
>>> type(dict)
<type 'type'>
>>> type(Exception)
<type 'classobj'>
>>> type(Exception())
<type 'instance'>

$ python2.5
Python 2.5.4 (r254:67916, Feb 17 2009, 23:11:16)
>>> type(Exception)
<type 'type'>
>>> type(Exception())
<type 'exceptions.Exception'>

В обеих версиях, как говорится в сообщении, исключениями могут быть классы, экземпляры (классов старого стиля) или строки (устаревшие).

Наконец, начиная с версии 2.5 иерархия исключений основана на новых классах стилей. Также разрешены экземпляры новых классов стилей, которые наследуются от BaseException. Но в 2.4 множественное наследование от Exception (старый класс стиля) и dict (новый класс стиля) приводит к новому классу стиля, который не допускается как исключение (смешение старых и новых классов стиля, вероятно, в любом случае плохо).

person lispmachine    schedule 29.05.2009
comment
Если то, что вы говорите, верно, мы должны иметь возможность наследовать от Dict и Exception в V3? - person e-satis; 29.05.2009
comment
Как упоминал Дейв Коста в Python 2.5 Exception и dict не могут находиться в одной и той же иерархии наследования, потому что они реализованы на C и хранятся по-разному. Это также относится к любому встроенному типу (класс нового стиля) даже в предыдущих версиях. Python 2.6 и 3, похоже, ничего не меняют в этой теме docs.python.org/3.0 /whatsnew/3.0.html - person lispmachine; 29.05.2009

Используйте collections.UserDict, чтобы избежать конфликтов метаклассов:

class ConstraintFailureSet(coll.UserDict, Exception):
        """
            Container for constraint failures. It act as a constraint failure itself
            but can contain other constraint failures that can be accessed with a dict syntax.
        """

        def __init__(self, **failures) :
            coll.UserDict.__init__(self, failures)
            Exception.__init__(self)


print( isinstance(ConstraintFailureSet(), Exception)) #True
raise ConstraintFailureSet()
person codeMonkey    schedule 17.12.2015