Skip to content

Function + @overload cause "Single overload definition, multiple required" #5047

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
ikonst opened this issue May 14, 2018 · 9 comments
Closed

Comments

@ikonst
Copy link
Contributor

ikonst commented May 14, 2018

Assume a function like:

def get_item(key: str, default: Optional[Item] = None) -> Optional[Item]:
   # implementation goes here
   ...

Now let's assume we want to enhance typing through an @overload by changing it to:

@overload
def get_item(key: str) -> Item:
   ...

def get_item(key: str, default: Optional[Item] = None) -> Optional[Item]:
   # implementation goes here
   ...

mypy will complain with:

error: Single overload definition, multiple required

This check makes sense for a .pyi file where you would expect multiple @overloads on equal footing, but in this case the original declaration might as well serve as an "overload".

A workaround is obviously to add:

@overload
def get_item(key: str, default: Optional[Item]) -> Optional[Item]:
   ...

which adds needless verbosity.

@Michael0x2a
Copy link
Collaborator

I'm a little bit confused about your type signature here: if 'default' is set to None, doesn't that mean the first overload alternative will always match? So, the only case where 'get_item' can return 'None' would be if 'default' is a non-None object?

Basically, I think your type signature is equivalent to the following:

@overload
def get_item(key: str, default: None = None) -> Item: ...
@overload
def get_item(key: str, default: Item) -> Optional[Item]: ...
def get_item(key: str, default: Optional[Item] = None) -> Optional[Item]:
    # implementation goes here

In any case, I think you usually want your overload signatures to be "specializations" of the implementation signature. We don't really want to force users to have the implementation signature be a valid overload alternative in that case.

@gvanrossum
Copy link
Member

I suspect that the OP intended something like this:

@overload
def get_item(key: str) -> Optional[Item]: ...
@overload
def get_item(key: str, default: None) -> Optional[Item]: ...
@overload
def get_item(key: str, default: Item) -> Item: ...
def get_item(key: str, default: Optional[item]) -> Optional[Item]:
    # implementation goes here

In any case we don't have plans to make it easier to shoot yourself in the foot here.

@raabf
Copy link

raabf commented Jul 12, 2019

If someone comes over this issue: The intended way of writing two different signatures, is to have them in two @overload signatures. And the actual function header for the implementation should be empty or should include the merge of all signatures with Union:

@overload
def get_item(key: str) -> Item:
   ...

@overload
def get_item(key: str, default: Optional[Item] = None) -> Optional[Item]:
   ...

def get_item(key, default = None):
   # implementation goes here
   ...

That's why there must be at least two @overload signatures and the error error: Single overload definition, multiple required will else arise.

See also #72 , #1136 , and #3360 .

@audoh
Copy link

audoh commented Aug 12, 2021

There's a good use case for single overloads in having a stricter signature than the actual implementation:

from typing import Any, Dict, overload

@overload
def create_safe_thing(*, field_a: int, field_b: bool) -> Dict[str, Any]:
    ...

def create_safe_thing(**kwargs: Any) -> Dict[str, Any]:
    return dict(kwargs)

print(create_safe_thing(field_a=3,field_b=True))
# >>> {'field_a': 3, 'field_b': True}

Would be nice to be able to do this as right now, when you're just chucking kwargs into another function, you have to choose between messy error-prone x=y for each kwarg but having a strongly typed signature, or having a weakly typed signature and non-error-prone code.

It's very easy while doing the former to accidentally do e.g.

from typing import Any, Dict, overload

def create_safe_thing(*, field_a: int, field_b: bool) -> Dict[str, Any]:
    return dict(field_a=field_a, field_b=field_a)

print(create_safe_thing(field_a=3,field_b=True))
# >>> {'field_a': 3, 'field_b': 3}

There are of course ways to achieve the best of both worlds, but the ones I can think of are quite tedious.

@hoodmane
Copy link

I did this:

@overload
def my_func(*, arg1: str, arg2: int, ...):
    ...

# Hack to prevent mypy error:
# "error: Single overload definition, multiple required"
class _EmptyType:
    __new__ = None # type: ignore

@overload
def my_func(a : _EmptyType):
    ...


def my_func(**kwargs):
   # implementation here.

I couldn't find any reference to a built in the typing or mypy docs.

@Antithesise
Copy link

Antithesise commented Jun 28, 2023

In 3.11 the typing.Never bottom type was implemented. This could perhaps be used to prevent static type-checkers from recommending these "empty" overloads. E.g.,

@overload
def foo_wrapper(a: str, b: int, c: Optional[float]=..., d: int=..., etc: Bool=...) -> int: ... # a, b, and foo args

@overload
def foo_wrapper(a: Never, b: Never, **kw: Never) -> Never: ... # don't recommend

def foo_wrapper(a, b, **kw):
    return self.foo(a, b, **kw)

At the time of writing, pyright (via pylance, vscode) seems only to ignore (not display to the user) overloads of methods with Never placed upon the self argument. The only case I can think of where this might be helpful is when writing a class with a method that wraps another method or function

@tachylatus
Copy link

tachylatus commented Feb 22, 2024

In 3.11 the typing.Never bottom type was implemented. This could perhaps be used to prevent static type-checkers from recommending these "empty" overloads. E.g.,

At the time of writing, pyright (via pylance, vscode) seems only to ignore (not display to the user) overloads of methods with Never placed upon the self argument. The only case I can think of where this might be helpful is when writing a class with a method that wraps another method or function

For those looking for a workaround prior to Python 3.11's typing.Never, annotating self with typing.NoReturn also seems to have the desired effect, but sadly causes a new mypy error, "The erased type of self "Never" is not a supertype of its class":

class Taint(dict):
    @overload
    def __init__(self, *, key: str, value: Optional[str] = None, effect: Union[Effect, str]): ...
    @overload
    def __init__(self: NoReturn) -> NoReturn: ...  # workaround for mypy single overload definition error
    def __init__(self, **kwargs):
        # implementation

@JelleZijlstra
Copy link
Member

Never and NoReturn should be perfectly equivalent, for what it's worth. (We generally think Never is a better name.)

@flo-zimmermann
Copy link

flo-zimmermann commented Nov 20, 2024

For anyone else who came here because they wanted to do something like @audoh (like I did):

There's a good use case for single overloads in having a stricter signature than the actual implementation:

from typing import Any, Dict, overload

@overload
def create_safe_thing(*, field_a: int, field_b: bool) -> Dict[str, Any]:
    ...

def create_safe_thing(**kwargs: Any) -> Dict[str, Any]:
    return dict(kwargs)

print(create_safe_thing(field_a=3,field_b=True))
# >>> {'field_a': 3, 'field_b': True}

typing.TypedDict plus typing.Unpack is probably the way to go:

from typing import Any, TypedDict, Unpack

class SafeThingKwargs(TypedDict):
    field_a: int
    field_b: bool

def create_safe_thing(**kwargs: Unpack[SafeThingKwargs]) -> dict[str, Any]:
    return dict(kwargs)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants