Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypedDict totality ignored when unpacked for function call #12754

Open
Zeckie opened this issue May 9, 2022 · 2 comments
Open

TypedDict totality ignored when unpacked for function call #12754

Zeckie opened this issue May 9, 2022 · 2 comments
Labels
bug mypy got something wrong topic-typed-dict

Comments

@Zeckie
Copy link

Zeckie commented May 9, 2022

Bug Report

When unpacking a typeddict for a function call, mypy doesn't seem to check the totality of the typeddict, and instead assumes the keys are all present.

from typing import TypedDict

def my_function(foo: int, bar: int, baz: str) -> None:
    pass

class MyTypedDict(TypedDict, total=False):
    foo: int
    bar: int
    baz: str
    
    
a: MyTypedDict = {'bar': 1}

my_function(**a) # Should be error here, as a doesn't contain foo or baz

https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=5444e1100000a8f1678fe9cbcfc23921

Expected Behavior

Mypy should not assume that keys are present when the TypedDict has total=False, and treat it as potentially having any combination of they keys present.

Actual Behavior

Mypy allows this, but when executed it gives the following error:

Traceback (most recent call last):
  File "<path>\typeddict1.py", line 17, in <module>
    my_function(**a)  # Should be error here, as a doesn't contain foo or baz
TypeError: my_function() missing 2 required positional arguments: 'foo' and 'baz'

#11753

@Zeckie Zeckie added the bug mypy got something wrong label May 9, 2022
@erictraut
Copy link

We need to consider whether it's best to err on the side of safety (with the possibility of false positives) or practicality (with the possibility of false negatives). There's no right answer, but whatever decision we make should be applied consistently.

Consider how mypy handles the unpack operator for iterables.

def my_function(a: int, b, int):
    ...

def func(b: list[int]):
    my_function(*b)

func([1])

This type checks without errors even though it generates an exception at runtime. So mypy chose the "practical" choice here rather than the "safe" choice. For consistency, it probably makes sense that it would do the same for unpacking of TypedDicts with fields that are not required.

@Zeckie Zeckie changed the title TypedDict totatity ignored when unpacked for function call TypedDict totality ignored when unpacked for function call May 9, 2022
@Zeckie
Copy link
Author

Zeckie commented May 9, 2022

I think that TypedDicts are a bit different to lists in this case, as they are more configurable. TypeDicts can specify which keys must be supplied vs those that can be omitted, whereas there is no way that I am aware of to specify a list that must always be a specified length.

This is how ensure that MyTypedDict always contains 'foo', but does not need to have a 'bar':

from typing import TypedDict

def my_function(foo: int, bar: int = 2) -> None:
    pass

class _MyTypedDict(TypedDict, total=True):
    foo: int

class MyTypedDict(_MyTypedDict, total=False):
    bar: int

a: MyTypedDict = {'foo': 1}
b: MyTypedDict = {'foo': 1, 'bar': 4}
c: MyTypedDict = {'bar': 4} # mypy - error: Missing key "foo" for TypedDict "MyTypedDict"

my_function(**a)
my_function(**b)

https://mypy-play.net/?mypy=0.931&python=3.11&flags=strict&gist=d884981b53cc85cc37b7203f0dffde7a

I think this situation is closer how mypy is strict with Optional (even to the point they can both be described with the word optional). If a variable could be None, it can't be used in a case that requires an int.

from typing import TypedDict

def my_function(foo: int, bar: int) -> None:
    pass

a: int = 1
b: int | None = None
my_function(a, b) # mypy - error: Argument 2 to "my_function" has incompatible type "Optional[int]"; expected "int"

https://mypy-play.net/?mypy=0.931&python=3.11&flags=strict&gist=192069ebb39cf087175008d34b88b8a5

Maybe there could be a strict (safe) vs relaxed (practical) unpacking configuration, with strict not allowing the list to be unpacked to be used with a fixed number of parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-typed-dict
Projects
None yet
Development

No branches or pull requests

3 participants