classification
Title: TypedDict subtypes ignore any other metaclasses in 3.9+
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: lukasz.langa, serhiy.storchaka, sobolevn, uriyyo
Priority: normal Keywords: patch

Created on 2021-08-15 12:23 by sobolevn, last changed 2021-09-28 14:59 by lukasz.langa.

Pull Requests
URL Status Linked Edit
PR 28057 open uriyyo, 2021-08-30 08:48
Messages (2)
msg399615 - (view) Author: Nikita Sobolev (sobolevn) * Date: 2021-08-15 12:23
Some context. I have a `User` class defined as a `TypedDict`:

```python
from typing import TypedDict

class User(TypedDict):
    name: str
    registered: bool
```

Now, I want to check if some `dict` is an instance of `User` like so: `isinstance(my_dict, User)`. But, I can't. Because it raises `TypeError('TypedDict does not support instance and class checks')`. 

Ok. Let's change `__instancecheck__` method then. We can only do this in a metaclass:

```python
from typing import _TypedDictMeta

class UserDictMeta(_TypedDictMeta):
    def __instancecheck__(cls, arg: object) -> bool:
        return (
            isinstance(arg, dict) and
            isinstance(arg.get('name'), str) and
            isinstance(arg.get('registered'), bool)
        )


class UserDict(User, metaclass=UserDictMeta):
    ...
```

It looks like it should work. It used to work like this in Python3.7 and Python3.8.

But since Python3.9 it does not work.
Let's try to debug what happens:

```python
print(type(UserDict))  # <class 'typing._TypedDictMeta'>
print(UserDict.__instancecheck__(UserDict, {}))  # TypeError: TypedDict does not support instance and class checks
```

It looks like my custom `UserDictMeta` is completely ignored.
And I cannot change how `__instancecheck__` behaves.

I suspect that the reason is in these 2 lines: https://github.com/python/cpython/blob/ad0a8a9c629a7a0fa306fbdf019be63c701a8028/Lib/typing.py#L2384-L2385

What's the most unclear in this behavior is that it does not match regular Python subclass patterns. Simplified example of the same behavior, using only primite types:

```python
class FirstMeta(type):
    def __instancecheck__(cls, arg: object) -> bool:
        raise TypeError('You cannot use this type in isinstance() call')


class First(object, metaclass=FirstMeta):
    ...

# User space:

class MyClass(First): # this looks like a user-define TypedDict subclass
    ...


class MySubClassMeta(FirstMeta):
    def __instancecheck__(cls, arg: object) -> bool:
        return True  # just an override example


class MySubClass(MyClass, metaclass=MySubClassMeta):
    ...

print(isinstance(1, MySubClass))  # True
print(isinstance(1, MyClass))  # TypeError
```

As you can see our `MySubClassMeta` works perfectly fine this way.

I suppose that this is a bug in `TypedDict`, not a desired behavior. Am I correct?
msg402781 - (view) Author: Ɓukasz Langa (lukasz.langa) * (Python committer) Date: 2021-09-28 14:59
The change to disallow this in Python 3.9 was deliberate, see BPO-40187 and its description.

Your attempt to make `isinstance()` work with TypedDict subclasses goes directly against this design. In fact, PEP 589 explicitly says that:

- Methods are not allowed, since the runtime type of a TypedDict object will always be just dict (it is never a subclass of dict).
- Specifying a metaclass is not allowed.

I'm -1 to allow this but I'll also wait for Serhiy to voice his opinion.
History
Date User Action Args
2021-09-28 14:59:25lukasz.langasetnosy: + serhiy.storchaka, lukasz.langa
messages: + msg402781
2021-08-30 08:48:02uriyyosetkeywords: + patch
nosy: + uriyyo

pull_requests: + pull_request26501
stage: patch review
2021-08-15 12:23:36sobolevncreate