Skip to content

mypy fails to consider the case where *args or **kwds contains zero elements #4001

Closed

Description

@yutak

Minimum repro:

object(*[])
object(**{})

This gives the following errors:

Too many arguments for "object"
Too many arguments for "object"

I think this is valid code and should pass the type check.

Context: I encountered this when I tried to write a multiple inheritance model where each class is passing arguments to the next parent (in terms of MRO):

class A(object):
    def __init__(self, a_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

class B(object):
    def __init__(self, b_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

class C(A, B):
    def __init__(self, c_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

C(a_arg=1, b_arg=2, c_arg=3)

# This code runs fine but gives three type errors saying:
#     Too many arguments for "__init__" of "object"

You cannot expect a static derivation order due to the dynamic nature of MRO, so it's hard to avoid this error from happening.

Activity

  1. gvanrossum commented on Sep 25, 2017

    @gvanrossum
    Member

    While your minimal repro indeed gives those errors (and probably shouldn't), in your actual example things are not so simple -- mypy only looks at your source code, it doesn't execute your code, so when it type-checks the line super().__init__(**kwds) in your full example it can't actually assume that the argument list for object() is empty -- this depends on the call site, and while you show a call that would indeed result in an empty argument list, there may be other calls to C() that end up passing a non-empty kwds to object(). Mypy type-checks calls purely on the basis of the given signature, and the signature for C() says that any keyword arguments are accepted.

  2. astrojuanlu commented on Feb 8, 2018

    @astrojuanlu
    Contributor

    Is this related to #4335?

  3. gvanrossum commented on Feb 9, 2018

    @gvanrossum
    Member

    @Juanlu001

    Is this related to #4335?

    I don't see how. Maybe you mistyped the issue number? #4335 is about multiple inheritance. This issue is about varargs function calls.

  4. astrojuanlu commented on Feb 9, 2018

    @astrojuanlu
    Contributor

    I was confused because the original example in this issue used multiple inheritance as well.

  5. ilevkivskyi commented on Sep 27, 2019

    @ilevkivskyi
    Member

    The first example object(*[]) now works correctly, second one still fails. Should be probably not hard to fix.

  6. momohatt commented on Mar 22, 2020

    @momohatt
    Contributor

    Hi, I'd like to work on this as my first issue and I have a question.
    Current implementation requires the type of kwargs in **kwargs to be a subtype of dict[str, <any>].

    mypy/mypy/checkexpr.py

    Lines 3790 to 3795 in 1677efd

    def is_valid_keyword_var_arg(self, typ: Type) -> bool:
    """Is a type valid as a **kwargs argument?"""
    if self.chk.options.python_version[0] >= 3:
    return is_subtype(typ, self.chk.named_generic_type(
    'typing.Mapping', [self.named_type('builtins.str'),
    AnyType(TypeOfAny.special_form)]))

    However, an empty dictionary {} is currently typed as dict[<nothing>, <nothing>] and fails this check, presumably because dictionary types are contravariant at the key position. Would it be better to type {} as dict[<any>, <nothing>] ?

    EDIT: I think #5580 is the same problem as this

  7. NeilGirdhar commented on May 4, 2020

    @NeilGirdhar
    Contributor

    This issue is explained with __init__, but the same problem happens when calling super in other methods like __init_subclass__.

  8. momohatt commented on Oct 22, 2020

    @momohatt
    Contributor

    I think it's difficult to judge whether a given dictionary is empty or not, so we cannot help but give up the "Too many arguments" check whenever there are arguments like **kwds (i.e. the third line of the following should not raise error)

    def f(): pass
    f(**{}) # ok
    f(**{'a':0}) # this should also be okay (false-negative)
    

    This is consistent with the case of lists and variable-length tuples:

    def f(): pass
    f(*[]) # ok
    f(*[1]) # also ok
    x: Tuple[int, ...] = ()
    f(*x) # ok
    

    At first I thought we can take advantage of the fact that an empty dictionary is typed as dict[<nothing>, <nothing>], but that will contradict with this solution (#5580 (comment)) on fixing the incorrect type checking on ** arguments...

  9. added a commit that references this issue on Aug 26, 2021
    ea7fed1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions