Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 838
Enrique Pérez Arnaud Avatar asked Jul 23 '20 12:07

Enrique Pérez Arnaud


People also ask

How do you use metaclass in Python?

To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.

Is object a metaclass in Python?

Python Classes can be Created Dynamically We have seen that everything in Python is an object, these objects are created by metaclasses. Whenever we call class to create a class, there is a metaclass that does the magic of creating the class behind the scenes.


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)
like image 78
chepner Avatar answered Oct 17 '22 02:10

chepner