[Python-ideas] Abstract metaclasses?

Erik Bray erik.m.bray at gmail.com
Wed Sep 10 22:59:03 CEST 2014


Hi all,

I recently ran across an interesting (mis?)-feature at least in
CPython that I couldn't find any specific justification for or
against.  The issue is that abstract *types*, while technically
possible, don't behave as abstract classes.

To exemplify, say I wanted to create a metaclass which itself has
ABCMeta as a metaclass, and which has some abstract classmethod
defined:

>>> import abc
>>> class Meta(type, metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(cls): pass
...

Now for all intents and purposes Meta *is* an abstract type:

>>> import inspect
>>> inspect.isabstract(Meta)
True
>>> Meta.__abstractmethods__
frozenset({'foo'})

However, nothing prevents Meta from being used as a metaclass for
another class, despite it being "abstract":

>>> class A(metaclass=Meta): pass
...
>>> A
<class '__main__.A'>


This is simply because the check for the Py_TYPFLAGS_IS_ABSTRACT flag
is implemented in object_new, which is overridden by type_new for type
subclasses.  type_new does not perform this check.

I'm perfectly fine if this is dismissed as too abstract or too
academic to be useful, but I will mention that this came up in a real
use case.  The use case is in a hierarchy of metaclasses involved in a
syntactic-sugary class factory framework involving creation of new
classes via operators.

So I just wonder if this is a bug that should be fixed, or at the very
least a feature request.  The IS_ABSTRACT flag check is cheap and easy
to add to type_new, in principle.

In the meantime a workaround, which doesn't seem too terrible, is
simply to define something I called AbstractableType:

>>> class AbstractableType(type):
...     def __new__(mcls, name, bases, members):
...         if inspect.isabstract(mcls):
...             raise TypeError(
...                 "Can't instantiate abstract type {0} with "
...                 "abstract methods {1}".format(
...                     mcls.__name__, ',
'.join(sorted(mcls.__abstractmethods__))))
...         return super(AbstractableType, mcls).__new__(mcls, name,
bases, members)
...

Now create an abstract metaclass with (meta-)metaclass abc.ABCMeta:

>>> class AbstractMeta(AbstractableType, metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(cls): pass
...

Creating a class with metaclass AbstractMeta fails as it should:

>>> class A(metaclass=AbstractMeta): pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __new__
TypeError: Can't instantiate abstract type AbstractMeta with abstract
methods foo

However, AbstractMeta can be subclassed with a concrete implementation:

>>> class ConcreteMeta(AbstractMeta):
...     def foo(cls): print("Concrete method")
...
>>> class A(metaclass=ConcreteMeta): pass
...
>>> A.foo()
Concrete method


Thanks,

Erik


More information about the Python-ideas mailing list