-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-85403: Make wraps retain type annotations #21392
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -647,6 +647,23 @@ The :mod:`functools` module defines the following functions: | |||||
on the wrapper function). :exc:`AttributeError` is still raised if the | ||||||
wrapper function itself is missing any attributes named in *updated*. | ||||||
|
||||||
When assigning the ``__annotations__`` attribute (when it's part of the | ||||||
``assigned`` list parameter), it will be populated only if missing some | ||||||
annotations as follows: | ||||||
|
||||||
* If the wrapper function defines it's own return type and parameter | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
annotations, those will be preserved. | ||||||
Comment on lines
+654
to
+655
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not very clear from this paragraph what this actually does. Suppose the wrapper has annotations for 'x' and 'y' and the wrapped function has annotations for 'z'. Does 'z' get added to the wrapper's annotations? Suppose the wrapper has no annotations, so it receives annotations for 'x', 'y' and 'z' from the wrapped function. What happens if the wrapper doesn't have arguments by those names? That is going to be confusing in some cases of further introspection. It almost sounds like |
||||||
|
||||||
* If the wrapper function does not annotate the return type, it will be | ||||||
copied from the wrapped if any. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
* If the wrapped function does not annotate any parameters, the annotations | ||||||
for them will be copied over from the wrapped if any. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
That ensures that any custom annotations from the wrapper will not be | ||||||
overridden by the wrapped, allowing to change the annotated types of | ||||||
the wrapped function. | ||||||
|
||||||
.. versionadded:: 3.2 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clearly you need a |
||||||
Automatic addition of the ``__wrapped__`` attribute. | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,14 +46,45 @@ def update_wrapper(wrapper, | |
updated is a tuple naming the attributes of the wrapper that | ||
are updated with the corresponding attribute from the wrapped | ||
function (defaults to functools.WRAPPER_UPDATES) | ||
|
||
There's a special treatment for the `__annotations__` attribute: | ||
* If the wrapper defines a return type, then that will be used, if not, | ||
the one from the wrapped function will be used if any. | ||
* If the wrapper defines any parameter types, those will be used, if | ||
not, the ones from the wrapped function will be used if any. | ||
""" | ||
for attr in assigned: | ||
try: | ||
value = getattr(wrapped, attr) | ||
except AttributeError: | ||
pass | ||
else: | ||
if attr == '__annotations__': | ||
continue | ||
setattr(wrapper, attr, value) | ||
|
||
if '__annotations__' in assigned: | ||
try: | ||
# Issue #41231: copy the annotations from wrapped, only if they are | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the bug number using the GH-NNN or bpo-NNN format. |
||
# not already defined in the wrapper | ||
if not wrapper.__annotations__: | ||
wrapper.__annotations__ = wrapped.__annotations__ | ||
# if the wrapper does not annotate the parameters, copy their | ||
# annotations over from the wrapped | ||
elif ('return' in wrapper.__annotations__ | ||
and len(wrapper.__annotations__) == 1): | ||
wrapper.__annotations__ = { | ||
**wrapped.__annotations__, | ||
**wrapper.__annotations__, | ||
} | ||
# if the wrapper does not annotate the return type, copy the | ||
# annotation from the wrapped | ||
elif ('return' not in wrapper.__annotations__ | ||
and 'return' in wrapped.__annotations__): | ||
wrapper.__annotations__['return'] = wrapped.__annotations__['return'] | ||
Comment on lines
+80
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh this is quite different from what I had understood from the docs (which I read before reading the code). I recommend changing the docs to more clearly describe what this does. |
||
except AttributeError: | ||
pass | ||
|
||
for attr in updated: | ||
getattr(wrapper, attr).update(getattr(wrapped, attr, {})) | ||
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
``update_wrapper``: Don't override the ``__annotations__`` of the wrapper | ||
function if it has any as it might have a different signature than the | ||
wrapped one, and using the wrapped might mask the actual types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it not part of
WRAPPER_ASSIGNMENTS
?