Skip to content

self not considered positional-only when followed by **kwargs #19051

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

Closed
osandov opened this issue May 8, 2025 · 4 comments
Closed

self not considered positional-only when followed by **kwargs #19051

osandov opened this issue May 8, 2025 · 4 comments
Labels
bug mypy got something wrong

Comments

@osandov
Copy link

osandov commented May 8, 2025

Bug Report

The following example using Concatenate to represent a method passes mypy in strict mode:

https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=5d5a1b27f2838371bee58b2d9c9a7721

from collections.abc import Callable
from typing import Any, Concatenate


def func(x: int) -> None:
    return


class MyClass:
    def method(self, x: int) -> None:
        return


def check[**P](f: Callable[P, None], m: Callable[Concatenate[Any, P], None]) -> None:
    return


check(func, MyClass.method)

However, if the signature has **kwargs, it fails with Argument 2 to "check" has incompatible type ... This is likely because "method of MyClass" has named arguments: "self".

https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=ef01c9fd1251d95be27ce96401ec7fe8

from collections.abc import Callable
from typing import Any, Concatenate


def func(**kwargs: Any) -> None:
    return


class MyClass:
    def method(self, **kwargs: Any) -> None:
        return


def check[**P](f: Callable[P, None], m: Callable[Concatenate[Any, P], None]) -> None:
    return


check(func, MyClass.method)

Explicitly making self positional-only fixes this, but in my case, I don't control the method signature (in my actual use case, the method is argparse.ArgumentParser.add_argument() and the function is copy_method_params() from python/cpython#121693).

To Reproduce

https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=ef01c9fd1251d95be27ce96401ec7fe8

Expected Behavior

There should be no errors.

Actual Behavior

There is an error:

main.py:18: error: Argument 2 to "check" has incompatible type "Callable[[Arg(MyClass, 'self'), KwArg(Any)], None]"; expected "Callable[[Any, KwArg(Any)], None]"  [arg-type]
main.py:18: note: This is likely because "method of MyClass" has named arguments: "self". Consider marking them positional-only
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: master, latest version
  • Mypy command-line flags: --strict
@osandov osandov added the bug mypy got something wrong label May 8, 2025
@sterliakov
Copy link
Collaborator

self is never positional-only unless declared as such. Consider:

from collections.abc import Callable
from typing import Any, Concatenate

class MyClass:
    def method(self, **kwargs: Any) -> None:
        return


def check[**P](func: Callable[Concatenate[Any, P], None], *args: P.args, **kwargs: P.kwargs) -> None:
    func(MyClass(), *args, **kwargs)

check(MyClass.method, self=1)  # E: Argument 1 to "check" has incompatible type "Callable[[Arg(MyClass, 'self'), KwArg(Any)], None]"; expected "Callable[[Any, KwArg(Any)], None]"  [arg-type]
# Runtime: TypeError: MyClass.method() got multiple values for argument 'self'

playground

@osandov
Copy link
Author

osandov commented May 8, 2025

self is never positional-only unless declared as such.

In that case, why doesn't my first example (https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=5d5a1b27f2838371bee58b2d9c9a7721) produce an error?

@sterliakov
Copy link
Collaborator

Because there's no fallback **kwargs or explicit self kw-only argument - your Concatenate strips the first argument, and you're left with one, positional-or-keyword argument x, there's no uncertainty whether self can be also passed as kwarg later.

Concatenate treats that as posonly, and def method(self, /, **kwargs) won't raise the runtime error, so we shouldn't reject it inside check.

@osandov
Copy link
Author

osandov commented May 8, 2025

Ok, that makes sense now, thanks! I found a solution by replacing the Callable with a Protocol that includes the self parameter.

@osandov osandov closed this as completed May 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants