Skip to content

API: Remove deprecated axes kwargs collision detection #18978

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/api/next_api_changes/deprecations/18978-LPS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pyplot.gca()
~~~~~~~~~~~~

Passing keyword arguments to ``.pyplot.gca`` will not be supported in a future
release.
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/development/18978-LPS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Changes to _AxesStack, preparing for its removal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The behavior of the internal ``.figure._AxesStack`` class has changed
significantly in the process of removing the old behavior of gca() with regard
to keyword arguments. When the deprecated behavior has been fully removed and
gca() no longer takes keyword arguments, the ``.figure._AxesStack`` class will
be removed.
18 changes: 18 additions & 0 deletions doc/users/next_whats_new/axes_kwargs_collision.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Changes to behavior of Axes creation methods (gca(), add_axes(), add_subplot())
-------------------------------------------------------------------------------

The behavior of the functions to create new axes (``.pyplot.subplot``,
``.figure.Figure.add_axes``, ``.figure.Figure.add_subplot``) has changed. In
the past, these functions would detect if you were attempting to create Axes
with the same keyword arguments as already-existing axes in the current figure,
and if so, they would return the existing Axes. Now, these functions will
always create new Axes.

Correspondingly, the behavior of the functions to get the current Axes
(``.pyplot.gca``, ``.figure.Figure.gca``) has changed. In the past, these
functions accepted keyword arguments. If the keyword arguments matched an
already-existing Axes, then that Axes would be returned, otherwise new Axes
would be created with those keyword arguments. Now, an exception is raised if
there are Axes and the current Axes were not created with the same keyword
arguments. In a future release, these functions will not accept keyword
arguments at all.
90 changes: 20 additions & 70 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class _AxesStack(cbook.Stack):

This stack stores ``key, (ind, axes)`` pairs, where:

* **key** is a hash of the args and kwargs used in generating the Axes.
* **key** is a hash of the args used in generating the Axes.
* **ind** is a serial index tracking the order in which Axes were added.

AxesStack is a callable; calling it returns the current Axes.
Expand All @@ -85,14 +85,6 @@ def get(self, key):
item = dict(self._elements).get(key)
if item is None:
return None
cbook.warn_deprecated(
"2.1",
message="Adding an axes using the same arguments as a previous "
"axes currently reuses the earlier instance. In a future "
"version, a new instance will always be created and returned. "
"Meanwhile, this warning can be suppressed, and the future "
"behavior ensured, by passing a unique label to each axes "
"instance.")
return item[1]

def _entry_from_axes(self, e):
Expand All @@ -114,18 +106,12 @@ def add(self, key, a):
"""
Add Axes *a*, with key *key*, to the stack, and return the stack.

If *key* is unhashable, replace it by a unique, arbitrary object.

If *a* is already on the stack, don't add it again, but
return *None*.
"""
# All the error checking may be unnecessary; but this method
# is called so seldom that the overhead is negligible.
cbook._check_isinstance(Axes, a=a)
try:
hash(key)
except TypeError:
key = object()

a_existing = self.get(key)
if a_existing is not None:
Expand Down Expand Up @@ -689,19 +675,12 @@ def add_axes(self, *args, **kwargs):
"add_axes() got multiple values for argument 'rect'")
args = (kwargs.pop('rect'), )

# shortcut the projection "key" modifications later on, if an axes
# with the exact args/kwargs exists, return it immediately.
key = self._make_key(*args, **kwargs)
ax = self._axstack.get(key)
if ax is not None:
self.sca(ax)
return ax

if isinstance(args[0], Axes):
a = args[0]
if a.get_figure() is not self:
raise ValueError(
"The Axes must have been created in the present figure")
key = self._make_key(*args)
else:
rect = args[0]
if not np.isfinite(rect).all():
Expand Down Expand Up @@ -848,7 +827,7 @@ def add_subplot(self, *args, **kwargs):
"the present figure")
# make a key for the subplot (which includes the axes object id
# in the hash)
key = self._make_key(*args, **kwargs)
key = self._make_key(*args)

else:
if not args:
Expand Down Expand Up @@ -1601,36 +1580,23 @@ def gca(self, **kwargs):
%(Axes)s

"""
if kwargs:
cbook.warn_deprecated(
"3.4",
message="Calling gca() with keyword arguments is deprecated. "
"gca() no longer checks whether the keyword arguments match "
"those with which the current axes were created. In a future "
"version, gca() will take no keyword arguments. The gca() "
"function should only be used to get the current axes, or if "
"no axes exist, create new axes with default keyword "
"arguments. To create a new axes with non-default arguments, "
"use plt.axes() or plt.subplot().")

ckey, cax = self._axstack.current_key_axes()
# if there exists an axes on the stack see if it matches
# the desired axes configuration
if cax is not None:

# if no kwargs are given just return the current axes
# this is a convenience for gca() on axes such as polar etc.
if not kwargs:
return cax

# if the user has specified particular projection detail
# then build up a key which can represent this
else:
projection_class, _, key = \
self._process_projection_requirements(**kwargs)

# let the returned axes have any gridspec by removing it from
# the key
ckey = ckey[1:]
key = key[1:]

# if the cax matches this key then return the axes, otherwise
# continue and a new axes will be created
if key == ckey and isinstance(cax, projection_class):
return cax
else:
cbook._warn_external('Requested projection is different '
'from current axis projection, '
'creating new axis with requested '
'projection.')
return cax

# no axes found, so create one which spans the figure
return self.add_subplot(1, 1, 1, **kwargs)
Expand Down Expand Up @@ -1706,28 +1672,12 @@ def _process_projection_requirements(

# Make the key without projection kwargs, this is used as a unique
# lookup for axes instances
key = self._make_key(*args, **kwargs)
key = self._make_key(*args)

return projection_class, kwargs, key

def _make_key(self, *args, **kwargs):
"""Make a hashable key out of args and kwargs."""

def fixitems(items):
# items may have arrays and lists in them, so convert them
# to tuples for the key
ret = []
for k, v in items:
# some objects can define __getitem__ without being
# iterable and in those cases the conversion to tuples
# will fail. So instead of using the np.iterable(v) function
# we simply try and convert to a tuple, and proceed if not.
try:
v = tuple(v)
except Exception:
pass
ret.append((k, v))
return tuple(ret)
def _make_key(self, *args):
"""Make a hashable key out of args."""

def fixlist(args):
ret = []
Expand All @@ -1737,7 +1687,7 @@ def fixlist(args):
ret.append(a)
return tuple(ret)

key = fixlist(args), fixitems(kwargs.items())
key = fixlist(args)
return key

def get_default_bbox_extra_artists(self):
Expand Down
7 changes: 5 additions & 2 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2410,10 +2410,13 @@ def polar(*args, **kwargs):
"""
# If an axis already exists, check if it has a polar projection
if gcf().get_axes():
if not isinstance(gca(), PolarAxes):
ax = gca()
if isinstance(ax, PolarAxes):
return ax
else:
cbook._warn_external('Trying to create polar plot on an axis '
'that does not have a polar projection.')
ax = gca(polar=True)
ax = axes(polar=True)
ret = ax.plot(*args, **kwargs)
return ret

Expand Down
30 changes: 18 additions & 12 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2387,28 +2387,34 @@ def _as_mpl_axes(self):
# testing axes creation with plt.axes
ax = plt.axes([0, 0, 1, 1], projection=prj)
assert type(ax) == PolarAxes
ax_via_gca = plt.gca(projection=prj)
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
ax_via_gca = plt.gca(projection=prj)
assert ax_via_gca is ax
plt.close()

# testing axes creation with gca
ax = plt.gca(projection=prj)
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
ax = plt.gca(projection=prj)
assert type(ax) == mpl.axes._subplots.subplot_class_factory(PolarAxes)
ax_via_gca = plt.gca(projection=prj)
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
ax_via_gca = plt.gca(projection=prj)
assert ax_via_gca is ax
# try getting the axes given a different polar projection
with pytest.warns(UserWarning) as rec:
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
ax_via_gca = plt.gca(projection=prj2)
assert len(rec) == 1
assert 'Requested projection is different' in str(rec[0].message)
assert ax_via_gca is not ax
assert ax.get_theta_offset() == 0
assert ax_via_gca.get_theta_offset() == np.pi
# try getting the axes given an == (not is) polar projection
with pytest.warns(UserWarning):
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
ax_via_gca = plt.gca(projection=prj3)
assert len(rec) == 1
assert 'Requested projection is different' in str(rec[0].message)
assert ax_via_gca is ax
plt.close()

Expand Down
31 changes: 22 additions & 9 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import matplotlib as mpl
from matplotlib import cbook, rcParams
from matplotlib.cbook import MatplotlibDeprecationWarning
from matplotlib.testing.decorators import image_comparison, check_figures_equal
from matplotlib.axes import Axes
from matplotlib.figure import Figure
Expand Down Expand Up @@ -154,30 +155,42 @@ def test_gca():
assert fig.add_axes() is None

ax0 = fig.add_axes([0, 0, 1, 1])
assert fig.gca(projection='rectilinear') is ax0
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
assert fig.gca(projection='rectilinear') is ax0
assert fig.gca() is ax0

ax1 = fig.add_axes(rect=[0.1, 0.1, 0.8, 0.8])
assert fig.gca(projection='rectilinear') is ax1
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
assert fig.gca(projection='rectilinear') is ax1
assert fig.gca() is ax1

ax2 = fig.add_subplot(121, projection='polar')
assert fig.gca() is ax2
assert fig.gca(polar=True) is ax2
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
assert fig.gca(polar=True) is ax2

ax3 = fig.add_subplot(122)
assert fig.gca() is ax3

# the final request for a polar axes will end up creating one
# with a spec of 111.
with pytest.warns(UserWarning):
# Changing the projection will throw a warning
assert fig.gca(polar=True) is not ax3
assert fig.gca(polar=True) is not ax2
assert fig.gca().get_subplotspec().get_geometry() == (1, 1, 0, 0)
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
# Changing the projection will raise an exception
fig.gca(polar=True)

fig.sca(ax1)
assert fig.gca(projection='rectilinear') is ax1
with pytest.warns(
MatplotlibDeprecationWarning,
match=r'Calling gca\(\) with keyword arguments is deprecated'):
assert fig.gca(projection='rectilinear') is ax1
assert fig.gca() is ax1


Expand Down