python abstractmethod with another baseclass breaks abstract functionality

后端 未结 2 1670
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-30 10:21

Consider the following code example

import abc
class ABCtest(abc.ABC):
    @abc.abstractmethod
    def foo(self):
             


        
相关标签:
2条回答
  • 2020-11-30 11:02

    I asked a similar question and based on user2357112 supports Monicas linked bug report, I came up with this workaround (based on the suggestion from Xiang Zhang):

    from abc import ABC, abstractmethod
    
    class Base(ABC):
        @abstractmethod
        def foo(self):
            pass
    
        @abstractmethod
        def bar(self):
            pass
    
        def __new__(cls, *args, **kwargs):
            abstractmethods = getattr(cls, '__abstractmethods__', None)
            if abstractmethods:
                msg = "Can't instantiate abstract class {name} with abstract method{suffix} {methods}"
                suffix = 's' if len(abstractmethods) > 1 else ''
                raise TypeError(msg.format(name=cls.__name__, suffix=suffix, methods=', '.join(abstractmethods)))
            return super().__new__(cls, *args, **kwargs)
    
    class Derived(Base, tuple):
        pass
    
    Derived()
    

    This raises TypeError: Can't instantiate abstract class Derived with abstract methods bar, foo, which is the original behaviour.

    0 讨论(0)
  • 2020-11-30 11:14

    Surprisingly, the test that prevents instantiating abstract classes happens in object.__new__, rather than anything defined by the abc module itself:

    static PyObject *
    object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        ...
        if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
            ...
            PyErr_Format(PyExc_TypeError,
                         "Can't instantiate abstract class %s "
                         "with abstract methods %U",
                         type->tp_name,
                         joined);
    

    (Almost?) all built-in types that aren't object supply a different __new__ that overrides object.__new__ and does not call object.__new__. When you multiple-inherit from a non-object built-in type, you inherit its __new__ method, bypassing the abstract method check.

    I don't see anything about __new__ or multiple inheritance from built-in types in the abc documentation. The documentation could use enhancement here.

    It seems kind of strange that they'd use a metaclass for the ABC implementation, making it a mess to use other metaclasses with abstract classes, and then put the crucial check in core language code that has nothing to do with abc and runs for both abstract and non-abstract classes.

    There's a report for this issue on the issue tracker that's been languishing since 2009.

    0 讨论(0)
提交回复
热议问题