Skip to content

Multiple inheritance triggers issues that cannot be resolved #4335

Open

Description

@jmh045000

When using multiple inheritance, the mixing of arguments to different base classes triggers warnings that cannot be resolved.

from typing import Any, Dict, Optional, Sequence, Tuple

class BaseClass(object):
    def __init__(self, foo: str, *args: Sequence[Any], **kwargs: Dict[str, Any]) -> None:
        self.foo = foo
        super().__init__(*args, **kwargs)

class Mixin(object):
    def __init__(self, bar: str, baz: Optional[str]=None, *args: Sequence[Any], **kwargs: Dict[str, Any]) -> None:
        self.bar = bar
        self.baz = baz or 'baz2'
        super().__init__(*args, **kwargs)

class Derived(BaseClass, Mixin):
    def __init__(self, foo: str, bar: str, other: str, *args: Sequence[Any], **kwargs: Dict[str, Any]) -> None:
        self.other = other
        super().__init__(foo=foo, bar=bar, *args, **kwargs)

d = Derived('foo', 'bar', 'other')

print(d.foo)
print(d.bar)
print(d.other)
print(d.baz)
% python3.6 test.py 
foo
bar
other
baz2
% mypy test.py                                                  
test.py:7: error: Too many arguments for "__init__" of "object"
test.py:13: error: Too many arguments for "__init__" of "object"
test.py:18: error: "__init__" of "BaseClass" gets multiple values for keyword argument "foo"
test.py:18: error: Argument 1 to "__init__" of "BaseClass" has incompatible type *Tuple[Sequence[Any], ...]; expected "str"
test.py:18: error: Argument 4 to "__init__" of "BaseClass" has incompatible type **Dict[str, Dict[str, Any]]; expected "str"
test.py:18: error: Argument 4 to "__init__" of "BaseClass" has incompatible type **Dict[str, Dict[str, Any]]; expected Sequence[Any]
test.py:18: error: Argument 3 to "__init__" of "BaseClass" has incompatible type "str"; expected Dict[str, Any]
% mypy --version
mypy 0.521
% python3.6 --version
Python 3.6.3

Activity

  1. gvanrossum commented on Dec 7, 2017

    @gvanrossum
    Member

    Please start by changing the type of all your **kwds to **kwds: Any; that's your first mistake.

  2. emmatyping commented on Dec 17, 2017

    @emmatyping
    Member

    Running on 0.560 and fixing the *arg and **kwarg types gives

    classtest.py:6: error: Too many arguments for "__init__" of "object"
    classtest.py:12: error: Too many arguments for "__init__" of "object"
    classtest.py:17: error: "__init__" of "BaseClass" gets multiple values for keyword argument "foo"
    

    It appears mypy is passing all of the arguments to BaseClass and some arguments to Mixin?

  3. gvanrossum commented on Dec 17, 2017

    @gvanrossum
    Member

    It's not specific to classes; the super call arguments are just passed to the first base class (which statically is the best thing to do, though not sound). The first two errors are because object() doesn't take arguments. The latter seems a bug that I can repro like this:

    def f(foo: str, bar: str) -> None:
        pass
    def g() -> None:
        args: tuple
        f(foo='', *args)  # E: "f" gets multiple values for keyword argument "foo"

    I think I've seen this before; there's something wrong with the way *args is checked in a call.

  4. ilevkivskyi commented on Dec 18, 2017

    @ilevkivskyi
    Member

    @gvanrossum Yes, I also have seen this. I think this may be a bug in map_actuals_to_formals, but I didn't investigate it in depth.

  5. elliott-beach commented on Dec 30, 2017

    @elliott-beach
    Contributor

    I think I've seen this before; there's something wrong with the way *args is checked in a call.

    @gvanrossum's repro gives the same error on python3 test.py, if you actually call g.

    This is my test.py:

    def f(foo: str, bar: str) -> None:
        pass
    
    def g(*args) -> None:
        f(foo='foo', *args)
    
    g("bar")
    

    In fact, it's just the usual error from following a keyword arg with a positional arg. As f requires a second argument, invoking g will always cause an error, so mypy's behavior is correct. Do I understand?

    Similarly, the error above is indicating that passing a fourth argument to the Derived constructor results in a TypeError. The way to resolve the error is to not pass *args to super in Derived (demonstration).

    As such I don't think there's a real issue here.

  6. gvanrossum commented on Dec 30, 2017

    @gvanrossum
    Member

    Ah, yes. That example gives a similar error when executed:

    $ python3 _.py
    Traceback (most recent call last):
      File "_.py", line 7, in <module>
        g("bar")
      File "_.py", line 5, in g
        f(foo='foo', *args)
    TypeError: f() got multiple values for argument 'foo'
    
  7. ilevkivskyi commented on Jan 3, 2018

    @ilevkivskyi
    Member

    Ah, yes. That example gives a similar error when executed:

    But the original code fails in mypy while it works at runtime. Here is the repro with obvious errors fixed:

    class BaseClass(object):
        def __init__(self, foo: str, *args: Any, **kwargs: Any) -> None:
            self.foo = foo
            super().__init__(*args, **kwargs)  # E: Too many arguments for "__init__" of "object"
    
    class Mixin(object):
        def __init__(self, bar: str, baz: Optional[str] = None, *args: Any, **kwargs: Any) -> None:
            self.bar = bar
            self.baz = baz or 'baz2'
            super().__init__(*args, **kwargs)  # E: Too many arguments for "__init__" of "object"
    
    class Derived(BaseClass, Mixin):
        def __init__(self, foo: str, bar: str, other: str, *args: Any, **kwargs: Any) -> None:
            self.other = other
            super().__init__(foo=foo, bar=bar, *args, **kwargs)  # E: "__init__" of "BaseClass" gets multiple values for keyword argument "foo"
    
    d = Derived('foo', 'bar', 'other')
  8. astrojuanlu commented on Feb 8, 2018

    @astrojuanlu
    Contributor

    Any workaround we can use while waiting for a fix?

  9. ilevkivskyi commented on Feb 8, 2018

    @ilevkivskyi
    Member

    Any workaround we can use while waiting for a fix?

    I think # type: ignore is your best bet here. (You can also mention an issue number to remember why you need it, like this ... # type: ignore # mypy issue 4335.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions