[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