Skip to content

Deprecate globals using module-level __getattr__. #20733

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

Merged
merged 1 commit into from
Aug 6, 2021

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Jul 24, 2021

This PR is mostly just to propose a pattern for defining module-level
__getattr__s for deprecating globals.

Relying on lru_cache rather than defining variables as global (see
change in __init__.py) avoids having to repeat twice the variable
name, and allows immediately returning its value without assigning it.
It means later accesses will be very slightly slower (because they'll
still go through the lru_cache layer), but that should hopefully be
negligible, and will mostly concern deprecated variables anyways.

One could consider adding a helper API in _api.deprecation that just
provides the decoration with @lru_cache and generates the
AttributeError message when needed, but that seems rather overkill.
(See #20620 (comment), in response to which this was written:
I don't have any "magic" idea this time :-), I now think the #10735
isn't really worth it.)

The change in deprecation.py allows one to skip obj_name and get a
message like "foo is deprecated..." rather than "The foo is
deprecated...".

PR Summary

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

@anntzer anntzer added this to the v3.5.0 milestone Jul 24, 2021
except TypeError as exc:
cursord = {} # deprecated in Matplotlib 3.5.

@functools.lru_cache(None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@functools.lru_cache(None)
# module-level depredations
@functools.lru_cache(None)

Since we don't make a dedicated function which could give the code segment a name, let's at least use a comment as a standard convention for explaining what this is for. - Also for the other uses of this pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you want that to be for matplotlib/__init__.py, which also uses __getattr__ for __version__?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity, you can leave that without a comment. Part of the intent of the comment is to easily see that the whole construct serves the deprecation and can be removed when expiring. That's not the case for the matplotlib.__init__.py __getattr__.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just put the comment in the if block.

This PR is mostly just to propose a pattern for defining module-level
`__getattr__`s for deprecating globals.

Relying on lru_cache rather than defining variables as `global` (see
change in `__init__.py`) avoids having to repeat *twice* the variable
name, and allows immediately returning its value without assigning it.
It means later accesses will be very slightly slower (because they'll
still go through the lru_cache layer), but that should hopefully be
negligible, and will mostly concern deprecated variables anyways.

One *could* consider adding a helper API in `_api.deprecation` that just
provides the decoration with `@lru_cache` and generates the
AttributeError message when needed, but that seems rather overkill.

The change in deprecation.py allows one to skip `obj_name` and get a
message like "foo is deprecated..." rather than "The foo  is
deprecated...".
@tacaswell tacaswell merged commit 3e32cc7 into matplotlib:master Aug 6, 2021
@anntzer anntzer deleted the dg branch August 6, 2021 16:51
lpsinger added a commit to lpsinger/matplotlib that referenced this pull request Aug 10, 2021
PR matplotlib#20733 added module-level `__getattr__` functions to several
modules. All of the functions with the exception of the one in
`matplotlib.style.core` had a terminal `raise AttributeError` to
handle unmatched attributes.

The omission in `matplotlib.style.core` was probably
unintentional; it results in confusing and buggy behavior
such as:

```pycon
>>> import matplotlib.style.core
>>> if hasattr(matplotlib.style.core, '__warningregistry__'):
...     del matplotlib.style.core.__warningregistry__
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: __warningregistry__
```

This causes problems in the unit tests for astropy affiliated
packages. See astropy/astropy#12038.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants