class ElectionMethod(namedtuple("ElectionMethod", ("voting_method", "attribution_method")), abc.ABC):
"""Type regrouping a voting method and an attribution method."""
__slots__ = ()
__lt__ = __gt__ = __le__ = __ge__ = lambda self, other: NotImplemented
def election(self, *args, **kwargs):
return self.attribution_method.attrib(self.voting_method.vote(*args, **kwargs))
I want to make a typed version of this, using typing.NamedTuple instead of collections.namedtuple. But it doesn’t work, for reasons that are obscure to me.
class ElectionMethod(NamedTuple, abc.ABC):
"""Type regrouping a voting method and an attribution method."""
voting_method: voting.VotingMethod
attribution_method: attribution.Attribution
__lt__ = __gt__ = __le__ = __ge__ = lambda self, other: NotImplemented
def election(self, *args, **kwargs):
return self.attribution_method.attrib(self.voting_method.vote(*args, **kwargs))
The error message is TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. Same issue when I used metaclass=abc.ABCMeta.
So, I wanted to make a subclass of the two metaclasses, and have that as the metaclass. But :
>>> type("NamedABC", (abc.ABCMeta, type(NamedTuple)), {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type 'function' is not an acceptable base type
I don’t get it.
How can I make the equivalent of the first class I made, but using NamedTuple ? Is there a way to replace abc.ABC ?
NamedTuple is a class factory function that returns an instance of collections.namedtuple that delegates its MRO resolution to NamedTupleMeta:
So NamedTupleMeta is the metaclass you should merge abc.ABCMeta with instead:
import abc
from typing import NamedTuple, NamedTupleMeta
class ABCNamedTupleMeta(abc.ABCMeta, NamedTupleMeta):
pass
class Foo(NamedTuple, metaclass=ABCNamedTupleMeta):
bar: int
baz: str
print(Foo(1, 'Baz').baz) # outputs Baz
>>> from typing import NamedTuple, NamedTupleMeta # type: ignore
>>> import abc
>>> class ElectionMethod(NamedTuple, metaclass=type("TheMeta", (abc.ABCMeta, NamedTupleMeta), {})):
... """Type regrouping a voting method and an attribution method."""
... voting_method: int
... attribution_method: int
... __lt__ = __gt__ = __le__ = __ge__ = lambda self, other: NotImplemented
...
>>> ElectionMethod.register
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'ElectionMethod' has no attribute 'register'
The namedtuple interface seems to survive, but the ABC one doesn’t. And inheriting from abc.ABC as well as having the metaclass raises an exception saying that “can only inherit from a NamedTuple type and Generic”.
Yes, this is because NamedTuple as a class factory returns a subclass of tuple, which has a metaclass of type, completely discarding the specified metaclass.
You can work around the behavior by patching builtins.tuple with a subclass that has ABCMeta as its metaclass:
import abc
from typing import NamedTuple
from unittest.mock import patch
class ABCTuple(tuple, metaclass=abc.ABCMeta):
...
with patch('builtins.tuple', ABCTuple):
class Foo(NamedTuple):
bar: int
baz: str
def foo(self):
pass
Foo.register(str)
print(issubclass(str, Foo)) # outputs True
print(Foo(1, 'Baz').baz) # outputs Baz