Skip to content

Fix: restore make_axes to accept a tuple of axes #24408

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

Conversation

PiotrStrzelczykX
Copy link
Contributor

@PiotrStrzelczykX PiotrStrzelczykX commented Nov 9, 2022

PR Summary

Allow to pass a tuple of axes as "ax" parameter of make_axes function.
It restores functionality available in this function, before memory leak fix
(in PR: #22089 ).

PR Checklist

Tests and Styling

  • [N/A] Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • [N/A] New plotting related features are documented with examples.

Release Notes

  • [N/A] New features are marked with a .. versionadded:: directive in the docstring and documented in doc/users/next_whats_new/
  • [N/A] API changes are marked with a .. versionchanged:: directive in the docstring and documented in doc/api/next_api_changes/
  • [N/A] Release notes conform with instructions in next_whats_new/README.rst or next_api_changes/README.rst

@PiotrStrzelczykX PiotrStrzelczykX force-pushed the pstrzelcz-fix-make_axes-parameters branch from ee2ee26 to 3eba834 Compare November 9, 2022 12:53
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a while, please feel free to ping @matplotlib/developers or anyone who has commented on the PR. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@story645
Copy link
Member

story645 commented Nov 9, 2022

Hi @PiotrStrzelczykX, thanks for the PR! Can you please provide a little more context, like a link to the PR where this memory leak went fix was implemented or to an open issue, if it exists, discussing the problem you are trying to solve here?
Thanks!

@PiotrStrzelczykX
Copy link
Contributor Author

Hello @story645, I added link to PR which changed this behavior.
I didn't prepare issue, because fix is so obvious.
I've spotted a issue after upgrading matplotlib:

AttributeError: 'tuple' object has no attribute 'get_figure' 
Error_place:
  File 'C:\\Python310\\lib\\site-packages\\matplotlib\\colorbar.py':1446 in make_axes
  fig = parents[0].get_figure()

and prepared this fix.

@jklymak
Copy link
Member

jklymak commented Nov 9, 2022

Please add a test and maybe also include your test code in the issue. If we broke something once it is because there is no test.

@jklymak
Copy link
Member

jklymak commented Nov 9, 2022

Actually I guess we should discuss if we want to accept a tuple. That may have worked before, but it wasn't documented, or particularly intended.

@ksunden
Copy link
Member

ksunden commented Nov 9, 2022

General typing advice is to be open about what you accept and strict about what you output.

parents is used to a) get parents[0] and b) loop over (twice)

Perhaps we should be testing/documenting as collections.abc.Sequence instead (which would get both list and tuple, as well as anything else which quacks like we use it)?

@jklymak
Copy link
Member

jklymak commented Nov 9, 2022

Sure that sounds reasonable. However, being permissive is tricky practically when you are trying to write type checks etc, and for testing, since we can't check everything that quacks to make sure we don't break it.

@tacaswell
Copy link
Member

I suspect the right check here is to see if it is an iterable and special case if it is an array. Maybe right path is something like

if is_iterable(parents):
    if isinstance(parents, np.array):
         list(parents.flat)
    else:
        parents = list(parents)
else:
    parents = [parents]

thus we come out of this block it is definitly a list, we special case the np.array case (which might break with some future numpy-clone), and we let Python do the coerces to list for us.

@PiotrStrzelczykX
Copy link
Contributor Author

@tacaswell checking is_iterable(parents) is not firm, as there is also parents[0] access, checking is_instance(parents, collections.abc.Sequence) (suggested by @ksunden) would be better.

@PiotrStrzelczykX PiotrStrzelczykX force-pushed the pstrzelcz-fix-make_axes-parameters branch 3 times, most recently from 95621c8 to 09a58c5 Compare November 10, 2022 11:21
@PiotrStrzelczykX
Copy link
Contributor Author

Please add a test and maybe also include your test code in the issue. If we broke something once it is because there is no test.

@jklymak test added.

I not insist on this change -- I thought that it may be useful for many users so I push it, but if you think it may be incompatible just discard it.

@rcomer
Copy link
Member

rcomer commented Nov 15, 2022

I think this description in Figure.colorbar should also be updated for consistecy

ax : `~.axes.Axes` or list or `numpy.ndarray` of Axes, optional
One or more parent axes from which space for a new colorbar axes
will be stolen, if *cax* is None. This has no effect if *cax* is
set.

@rcomer
Copy link
Member

rcomer commented Nov 15, 2022

@tacaswell’s suggestion of converting any iterable to a list would allow the user to pass dict.values(), where dict comes from subplot_mosaic. That seems worth having to me.

Allow to pass a tuple of axes as "ax" parameter of make_axes function

Signed-off-by: Strzelczyk, Piotr <piotr.strzelczyk@intel.com>
@PiotrStrzelczykX PiotrStrzelczykX force-pushed the pstrzelcz-fix-make_axes-parameters branch from 09a58c5 to a928cec Compare November 18, 2022 07:45
Copy link
Member

@rcomer rcomer left a comment

Choose a reason for hiding this comment

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

@PiotrStrzelczykX thanks for the update. The docstrings all look consistent with the change now.

I still think that if we support all sequences, it is worth expanding to support all iterables:

  • as @tacaswell notes, containing questions about types in that initial loop and always coming out of that loop with a list is more robust.
  • dict.values() seems like a likely thing to want to pass (and is not a sequence).
  • I suspect that a lot of users would not know off hand the difference between a sequence and an iterable (I always have to look it up!) so it’s just easier from the user point of view if the more general object is supported.

Having said all that, I am just a contributor and have no authority here, so we need to wait for a decision from the core developers.

@jklymak
Copy link
Member

jklymak commented Nov 18, 2022

I think we should actually figure out a consistent way to deal with itterables and perhaps factor it into _api so that we do it consistently in different places. This came up recently in another PR as well (sorry I forget which one)

I wonder if co-ercing to numpy array first is a good consistent way to do this? Then we are just relying on their broadcasting rules, which is easy to explain. eg parents = list(np.asarray(parents).flat). Or we could even just leave it as an array?

@efiring
Copy link
Member

efiring commented Dec 1, 2022

list() does the right thing for dictionaries, while np.asarray does not:

In [3]: d = dict(a=1, b=2)

In [4]: import numpy as np

In [5]: np.asarray(d.values())
Out[5]: array(dict_values([1, 2]), dtype=object)

In [6]: list(d.values())
Out[6]: [1, 2]

@ksunden
Copy link
Member

ksunden commented Dec 1, 2022

@tacaswell checking is_iterable(parents) is not firm, as there is also parents[0] access, checking is_instance(parents, collections.abc.Sequence) (suggested by @ksunden) would be better.

The code provided by @tacaswell enforces that it is a list (which can be indexed) when parents[0] is called, so it is perfectly fine for any (finite) iterable. So that code is in fact more general, and will accept more inputs as valid.

@efiring
Copy link
Member

efiring commented Dec 1, 2022

@PiotrStrzelczykX, thank you for this PR. In fact, I tripped over this (not accepting a tuple argument) myself a week or two ago. We will be happy to accept the PR either as-is, or after one more change, if you choose to make it: specifically, the suggestion by @tacaswell, and endorsed by @rcomer and myself, to broaden the supported types to include iterables, like dict.values(). Fleshed out, it becomes

if np.iterable(parents):
    if isinstance(parents, np.ndarray):
         parents = list(parents.flat)
    else:
        parents = list(parents)
else:
    parents = [parents]

If you do choose to make this change, then where you have "sequence" in the docstring it would become the more general "iterable".

It is possible that some time later, after merging the PR, we might factor this argument-handling functionality out for use in other places; but we don't want to stall the PR while hashing out larger questions of API design and factoring.

@efiring
Copy link
Member

efiring commented Dec 1, 2022

Also, if you choose to make the change, you could add a line to the test:

fig.colorbar(im, ax=dict(a=ax[0], b=ax[1]).values())

@tacaswell tacaswell added this to the v3.7.0 milestone Dec 4, 2022
@rcomer
Copy link
Member

rcomer commented Dec 13, 2022

I just noticed that the constrained layout guide states

If you specify a list of axes (or other iterable container) to the ax argument of colorbar, constrained_layout will take space from the specified axes.

https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html#colorbars

@PiotrStrzelczykX could you let us know if you intend to take this any further? As @efiring said, we are happy to merge as is. However, it would be better not to merge yet if you are planning to push further changes.

@tacaswell
Copy link
Member

I'm going to merge this on my approval and the comments from @efiring and @rcomer and then open up a follow on PR with the additional changes.

@tacaswell tacaswell merged commit 011a3f7 into matplotlib:main Dec 15, 2022
@tacaswell
Copy link
Member

Thank you for your work on this @PiotrStrzelczykX and congratulations on your first merged Matplotlib PR 🎉 I hope we hear from you again!

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

Successfully merging this pull request may close these issues.

8 participants