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

Multiple inheritance triggers issues that cannot be resolved #4335

Open
jmh045000 opened this issue Dec 7, 2017 · 9 comments
Open

Multiple inheritance triggers issues that cannot be resolved #4335

jmh045000 opened this issue Dec 7, 2017 · 9 comments
Labels
bug mypy got something wrong priority-1-normal topic-inheritance Inheritance and incompatible overrides

Comments

@jmh045000
Copy link

jmh045000 commented Dec 7, 2017

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
@gvanrossum
Copy link
Member

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

@emmatyping
Copy link
Collaborator

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?

@emmatyping emmatyping added bug mypy got something wrong priority-1-normal labels Dec 17, 2017
@gvanrossum
Copy link
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.

@ilevkivskyi
Copy link
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.

@elliott-beach
Copy link
Contributor

elliott-beach commented Dec 30, 2017

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.

@gvanrossum
Copy link
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'

@ilevkivskyi
Copy link
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')

@ilevkivskyi ilevkivskyi reopened this Jan 3, 2018
@astrojuanlu
Copy link
Contributor

Any workaround we can use while waiting for a fix?

@ilevkivskyi
Copy link
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.)

@AlexWaygood AlexWaygood added the topic-inheritance Inheritance and incompatible overrides label Apr 3, 2022
ilevkivskyi added a commit that referenced this issue Nov 16, 2022
Fixes #12344 

FWIW this is unsafe (since we don't know where the mixin will appear in
the MRO of the actual implementation), but the alternative is having
annoying false positives like this issue and e.g.
#4335
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-1-normal topic-inheritance Inheritance and incompatible overrides
Projects
None yet
Development

No branches or pull requests

7 participants