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

functools.update_wrapper will require changes for PEP 649 compatibility #21

Open
ncoghlan opened this issue Mar 13, 2024 · 1 comment
Open

Comments

@ncoghlan
Copy link

ncoghlan commented Mar 13, 2024

(Adding the issue here rather than the CPython repo since the PEP 649 implementation hasn't landed yet)

Since Python 3.2, functools.update_wrapper has eagerly copied o.__annotations__ from the wrapped object to the wrapper object.

This will do the wrong thing under PEP 649, since it will trigger early evaluation of the annotations on wrapped functions (and hence potentially fail if the annotations contain forward references).

I think the following changes will be sufficient to resolve the incompatibility:

  • remove __annotations__ from the default WRAPPER_ASSIGNMENTS list
  • set __annotate__ on the wrapper object as described below
  • update function docs accordingly

For maximum compatibility, I think the wrapper annotations will need to be retrieved as follows:

    def __annotate__(format):
        if format == 1:
            return wrapped.__annotations__ # Ensure previously cached annotations are respected
        import inspect # Ugh, maybe the guts of `get_annotations` could live down in `functools`?
        return inspect.get_annotations(wrapped, format)
    wrapper.__annotate__ = __annotate__

I initially thought we could just add __annotate__ to WRAPPER_ASSIGNMENTS, but I realised that doesn't play nice if a previous decorator has already eagerly evaluated the annotations, and perhaps modified them (in which case, wrapped.__annotate__ may even be None if the other decorator has been updated to behave as PEP 649 recommends).

The comment in the implementation sketch relates to the fact that inspect imports functools, so having functools depend on an inspect API introduces a circular dependency. Perhaps the piece that update_wrapper needs could be factored out to a lower level functools._get_annotations helper API that inspect.get_annotations also invokes? If we did that, the PEP 649 compatible update_wrapper code would become:

    def __annotate__(format):
        return _get_annotations(wrapped, format)
    wrapper.__annotate__ = __annotate__
@ncoghlan
Copy link
Author

inspect also pulls in many more modules as dependencies than functools does:

$ python3 -c 'import sys; print(len(sys.modules))'
32
$ python3 -c 'import sys; import functools; print(len(sys.modules))'
42
$ python3 -c 'import sys; import inspect; print(len(sys.modules))'
67

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

1 participant