You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Improve help() by making typing.overload() information accessible at runtime #89263
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2021-09-04.18:03:28.637>labels= ['3.11']
title='Improve help() by making typing.overload() information accessible at runtime'updated_at=<Date2022-03-28.22:36:21.459>user='https://github.com/rhettinger'
I agree that this would be nice to have, but wonder how help() could access that information. The two @overload definitions will be overwritten by the non-overload one at runtime, and hence will ever been seen by help().
There is a similar issue with functools.singledispatch
>>> from functools import singledispatch
>>> @singledispatch
... def flip(x: str) -> int:
... """Signature when given a string"""
... return int(x)
...
>>> @flip.register
... def _(x: int) -> str:
... """Signature when given an int"""
... return str(x)
...
>>> flip(5)
'5'
>>> flip('5')
5
>>> help(flip)
Help on function flip in module __main__:
flip(x: str) -> int
Signature when given a string
The two @overload definitions will be overwritten by
the non-overload one at runtime, and hence will ever
been seen by help().
We can fix this by adding an __overloads__ attribute. The overload decorator can accumulate the chain in an external namespace and function creation can move that accumulation into the new attribute.
changed the title [-]Teach help about typing.overload()[/-][+]Improve help() by making typing.overload() information accessible at runtime[/+]on Sep 5, 2021
changed the title [-]Teach help about typing.overload()[/-][+]Improve help() by making typing.overload() information accessible at runtime[/+]on Sep 5, 2021
Note, I'm not proposing a create_function() decorator. That is just for the proof of concept. The actual logic would go into normal function creation, the same place that __annotations__ gets added.
Also, there may be a better place than func.__globals__ to accumulate the overloads. For the proof-of-concept, it was just the easiest way to go.
) simply returns a dummy function and throws away the decorated function. This makes it virtually impossible for type checkers using the runtime function object to find overloads specified at runtime.
In pyanalyze, I worked around this by providing a custom @overload decorator, working something like this:
A full implementation will need more error handling.
I'd like to add something like this to typing.py so that other tools can also use this information.
---
With my suggested solution, help() would need to call typing.get_overloads_for() to get any overloads for the function. Unlike Raymond's suggestion, we would not need to change the function creation machinery.
Sounds good to me. (I don’t care what happens at runtime but I want to
support the folks who do.)--
--Guido (mobile)
TeamSpen210 commented on Feb 21, 2022
TeamSpen210mannequin
Mannequin
More actions
I'm not sure a get_overloads() function potentially called after the fact would fully work - there's the tricky case of nested functions, where the overload list would need to be somehow cleared to ensure every instantiation doesn't endlessly append to the same list. It's probably also desirable to weakref it (or make it an attribute) so they can be decrefed if the function isn't being used.
I'm OK with not fully supporting overloads created in nested functions; that's a pretty marginal use case. But it's true that my proposed implementation would create a memory leak if someone does do that. I don't immediately see a way to fix that with weakrefs. Maybe we need to put something in the defining namespace, as Raymond suggested.
TeamSpen210 commented on Feb 22, 2022
TeamSpen210mannequin
Mannequin
More actions
Had a potential thought. Since the only situation we care about is overload being used on function definitions in lexical order, valid calls are only that on definitions with ascending co_firstlineno counts. Expanding on Jelle's solution, the overload() decorator could compare the current function's line number to the first in the list, and if it's <= clear out the list (we're re-defining). Then repeated re-definitions wouldn't duplicate overloads.
The other change I'd suggest is to make get_overloads_for() first check __overloads__, then only if not present pop from the _overloads dict and assign to that attribute. That way if code calls get_overloads_for() at least once, the function will be referring to the actual overloads created at the same time. It'd also get garbage collected then when the function dies. It also means you could manually assign to add overloads to any callable.
I'd dearly like better introspection tools for functions decorated with @overload, but I'd rather have a solution where:
inspect.signature doesn't have to import typing. That doesn't feel worth it for users who aren't using typing.overload, but inspect.signature would have to import typing whether or not @overload was being used, in order to *check* whether @overload was being used.
The solution could be reused by, and generalised to, other kinds of functions that have multiple signatures.
If we create an __overloads__ dunder that stored the signatures of multi-signature functions, as Raymond suggests, inspect.signature could check that dunder to examine whether the function is a multi-dispatch signature, and change its representation of the function accordingly. This kind of solution could be easily reused by other parts of the stdlib, like @functools.singledispatch, and by third-party packages such as plum-dispatch, multipledispatch, and Nikita's dry-python/classes library.
So, while it would undoubtedly be more complex to implement, I much prefer Raymond's suggested solution.
After #31716 is merged, I'd like to have a stab at writing a PR to have overloads shown in the output of help(). For now, it will probably be easiest to do that without tinkering with inspect.signature(), and instead only making changes to pydoc -- I have a few ideas of how to do that.
I was just playing around with the new get_overloads function, and it appears that it doesn't work if for functions defined in the interactive shell. This seems unfortunate -- it certainly took me by surprise.
This is because all functions defined in the interactive shell have the same line-number set on their code object, and the new overloads registry uses the co_firstlineno attribute of the code attribute to register overloads.
I initially thought that this would cause problems for using get_overloads to improve the output of help(). Maybe it's not so much of a problem, though, since most people won't be calling help() on functions defined in the interactive shell -- they'll be calling it on functions that they import from within the interactive shell. Moreover, using @overload for a function defined in the interactive shell is pretty weird, since a type checker isn't much use to you in the interactive shell.
It's a little unfortunate because it makes it harder for people to experiment with get_overloads() in the REPL. But I agree that it's not a big problem for people using help() to introspect library functions.
Since @overload is a static typing feature, why would you be typing it in the REPL in the first place?
If you read about get_overloads() in the What's New and want to try it out for yourself, you might write some overloads in the REPL to see it in action. Looks like that's essentially what happened to Alex.
Since @overload is a static typing feature, why would you be typing it in the REPL in the first place?
Yeah, this is why I think it might actuallly not be that much of a big deal, as I said above.
If you read about get_overloads() in the What's New and want to try it out for yourself, you might write some overloads in the REPL to see it in action. Looks like that's essentially what happened to Alex.
Yup. Experimenting with stuff in the REPL is generally a part of my process when I write patches for CPython. I was tinkering with modifying pydoc to use get_overloads, and was trying things out in the REPL. I was surprised when things didn't work as expected, and it took me a minute or two to figure out that the reason for the unexpected behaviour was that I was in the interactive shell.
Activity
rhettinger commentedon Sep 4, 2021
Python's help() function does not display overloaded function signatures.
For example, this code:
Currently gives this help:
What is desired is:
The overload() decorator is sufficient for informing a static type checker but insufficient for informing a user or editing tool.
ronaldoussoren commentedon Sep 5, 2021
I agree that this would be nice to have, but wonder how help() could access that information. The two @overload definitions will be overwritten by the non-overload one at runtime, and hence will ever been seen by help().
AlexWaygood commentedon Sep 5, 2021
There is a similar issue with
functools.singledispatchrhettinger commentedon Sep 5, 2021
We can fix this by adding an __overloads__ attribute. The overload decorator can accumulate the chain in an external namespace and function creation can move that accumulation into the new attribute.
----- Proof of concept -----
[-]Teach help about typing.overload()[/-][+]Improve help() by making typing.overload() information accessible at runtime[/+][-]Teach help about typing.overload()[/-][+]Improve help() by making typing.overload() information accessible at runtime[/+]rhettinger commentedon Sep 5, 2021
Note, I'm not proposing a create_function() decorator. That is just for the proof of concept. The actual logic would go into normal function creation, the same place that __annotations__ gets added.
Also, there may be a better place than func.__globals__ to accumulate the overloads. For the proof-of-concept, it was just the easiest way to go.
JelleZijlstra commentedon Feb 21, 2022
I made a similar suggestion in bpo-46821 (thanks Alex for pointing me to this older issue):
Currently, the implementation of @overload (
cpython/Lib/typing.py
Line 2211 in 59585d6
In pyanalyze, I worked around this by providing a custom @overload decorator, working something like this:
_overloads: dict[str, list[Callable]] = {}
A full implementation will need more error handling.
I'd like to add something like this to typing.py so that other tools can also use this information.
---
With my suggested solution, help() would need to call typing.get_overloads_for() to get any overloads for the function. Unlike Raymond's suggestion, we would not need to change the function creation machinery.
gvanrossum commentedon Feb 21, 2022
Sounds good to me. (I don’t care what happens at runtime but I want to
support the folks who do.)--
--Guido (mobile)
TeamSpen210 commentedon Feb 21, 2022
I'm not sure a get_overloads() function potentially called after the fact would fully work - there's the tricky case of nested functions, where the overload list would need to be somehow cleared to ensure every instantiation doesn't endlessly append to the same list. It's probably also desirable to weakref it (or make it an attribute) so they can be decrefed if the function isn't being used.
JelleZijlstra commentedon Feb 21, 2022
I'm OK with not fully supporting overloads created in nested functions; that's a pretty marginal use case. But it's true that my proposed implementation would create a memory leak if someone does do that. I don't immediately see a way to fix that with weakrefs. Maybe we need to put something in the defining namespace, as Raymond suggested.
TeamSpen210 commentedon Feb 22, 2022
Had a potential thought. Since the only situation we care about is overload being used on function definitions in lexical order, valid calls are only that on definitions with ascending co_firstlineno counts. Expanding on Jelle's solution, the overload() decorator could compare the current function's line number to the first in the list, and if it's <= clear out the list (we're re-defining). Then repeated re-definitions wouldn't duplicate overloads.
The other change I'd suggest is to make get_overloads_for() first check __overloads__, then only if not present pop from the _overloads dict and assign to that attribute. That way if code calls get_overloads_for() at least once, the function will be referring to the actual overloads created at the same time. It'd also get garbage collected then when the function dies. It also means you could manually assign to add overloads to any callable.
AlexWaygood commentedon Feb 22, 2022
I'd dearly like better introspection tools for functions decorated with @overload, but I'd rather have a solution where:
If we create an __overloads__ dunder that stored the signatures of multi-signature functions, as Raymond suggests, inspect.signature could check that dunder to examine whether the function is a multi-dispatch signature, and change its representation of the function accordingly. This kind of solution could be easily reused by other parts of the stdlib, like @functools.singledispatch, and by third-party packages such as plum-dispatch, multipledispatch, and Nikita's dry-python/classes library.
So, while it would undoubtedly be more complex to implement, I much prefer Raymond's suggested solution.
4 remaining items
AlexWaygood commentedon Apr 16, 2022
After #31716 is merged, I'd like to have a stab at writing a PR to have overloads shown in the output of
help(). For now, it will probably be easiest to do that without tinkering withinspect.signature(), and instead only making changes topydoc-- I have a few ideas of how to do that.gh-89263: Add typing.get_overloads (GH-31716)
AlexWaygood commentedon Apr 17, 2022
I was just playing around with the new
get_overloadsfunction, and it appears that it doesn't work if for functions defined in the interactive shell. This seems unfortunate -- it certainly took me by surprise.AlexWaygood commentedon Apr 17, 2022
This is because all functions defined in the interactive shell have the same line-number set on their code object, and the new overloads registry uses the
co_firstlinenoattribute of the code attribute to register overloads.I initially thought that this would cause problems for using
get_overloadsto improve the output ofhelp(). Maybe it's not so much of a problem, though, since most people won't be callinghelp()on functions defined in the interactive shell -- they'll be calling it on functions that they import from within the interactive shell. Moreover, using@overloadfor a function defined in the interactive shell is pretty weird, since a type checker isn't much use to you in the interactive shell.JelleZijlstra commentedon Apr 17, 2022
It's a little unfortunate because it makes it harder for people to experiment with
get_overloads()in the REPL. But I agree that it's not a big problem for people usinghelp()to introspect library functions.gvanrossum commentedon Apr 17, 2022
Since
@overloadis a static typing feature, why would you be typing it in the REPL in the first place?JelleZijlstra commentedon Apr 17, 2022
If you read about
get_overloads()in the What's New and want to try it out for yourself, you might write some overloads in the REPL to see it in action. Looks like that's essentially what happened to Alex.AlexWaygood commentedon Apr 17, 2022
Yeah, this is why I think it might actuallly not be that much of a big deal, as I said above.
Yup. Experimenting with stuff in the REPL is generally a part of my process when I write patches for CPython. I was tinkering with modifying
pydocto useget_overloads, and was trying things out in the REPL. I was surprised when things didn't work as expected, and it took me a minute or two to figure out that the reason for the unexpected behaviour was that I was in the interactive shell.py-typing-extensions: Update to 4.3.0
furkanonder commentedon May 9, 2023
@JelleZijlstra The issue seems to be solved. We can close the issue.
JelleZijlstra commentedon May 9, 2023
We added
typing.get_overloads, but I don't think we ended up makinghelp()look at this information yet.py-typing-extensions: Update to 4.3.0
py-typing-extensions: Update to 4.3.0