Abstract Base NamedTuple

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
1 Like

That doesn’t appear to be working.

>>> 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