How to type the __new__ method in a Python metaclass so that mypy is happy

Viewed 3049

I am trying to type the __new__ method in a metaclass in Python so that it pleases mypy. The code would be something like this (taken from pep-3115 - "Metaclasses in Python 3000" and stripped down a bit):

from __future__ import annotations

from typing import Type


# The metaclass
class MetaClass(type):

    # The metaclass invocation
    def __new__(cls: Type[type], name: str, bases: tuple, classdict: dict) -> type:
        result = type.__new__(cls, name, bases, classdict)
        print('in __new__')
        return result


class MyClass(metaclass=MetaClass):
    pass

With this, mypy complains, Incompatible return type for "__new__" (returns "type", but must return a subtype of "MetaClass"), pointing at the line def __new__.

I have also tried with:

def __new__(cls: Type[MetaClass], name: str, bases: tuple, classdict: dict) -> MetaClass:

Then mypy complains (about the return result line): Incompatible return value type (got "type", expected "MetaClass").

I have also tried with a type var (TSubMetaclass = TypeVar('TSubMetaclass', bound='MetaClass')) and the result is the same as using MetaClass.

Using super().__new__ instead of type.__new__ gave similar results.

What would be the correct way to do it?

1 Answers

First, the return type is MetaClass, not type. Second, you need to explicitly cast the return value, since type.__new__ doesn't know it is returning an instance of MetaClass. (Its specific return type is determined by its first argument, which isn't known statically.)

from __future__ import annotations

from typing import Type, cast


# The metaclass
class MetaClass(type):

    # The metaclass invocation
    def __new__(cls: Type[type], name: str, bases: tuple, classdict: dict) -> MetaClass:
        result = type.__new__(cls, name, bases, classdict)
        print('in __new__')
        return cast(MetaClass, result)


class MyClass(metaclass=MetaClass):
    pass

To use super, you need to adjust the static type of the cls parameter.

class MetaClass(type):

    # The metaclass invocation
    def __new__(cls: Type[MetaClass], name: str, bases: tuple, classdict: dict) -> MetaClass:
        result = super().__new__(name, bases, classdict)
        print('in __new__')
        return cast(MetaClass, result)
Related