Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0135146

Browse files
committed
Add warn-on-modification wrappers around Axes.* lists.
1 parent 333cce9 commit 0135146

File tree

2 files changed

+199
-14
lines changed

2 files changed

+199
-14
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import OrderedDict
2+
from collections.abc import MutableSequence
23
from contextlib import ExitStack
34
import functools
45
import inspect
@@ -1304,40 +1305,149 @@ def cla(self):
13041305

13051306
self.stale = True
13061307

1308+
class ArtistList(MutableSequence):
1309+
"""
1310+
A sublist of Axes children based on their type.
1311+
1312+
This exists solely to warn on modification. In the future, the
1313+
type-specific children sublists will be immutable tuples.
1314+
"""
1315+
def __init__(self, axes, prop_name, add_name,
1316+
valid_types=None, invalid_types=None):
1317+
"""
1318+
Parameters
1319+
----------
1320+
axes : .axes.Axes
1321+
The Axes from which this sublist will pull the children
1322+
Artists.
1323+
prop_name : str
1324+
The property name used to access this sublist from the Axes;
1325+
used to generate deprecation warnings.
1326+
add_name : str
1327+
The method name used to add Artists of this sublist's type to
1328+
the Axes; used to generate deprecation warnings.
1329+
valid_types : list of type, optional
1330+
A list of types that determine which children will be returned
1331+
by this sublist. If specified, then the Artists in the sublist
1332+
must be instances of any of these types. If unspecified, then
1333+
any type of Artist is valid (unless limited by
1334+
*invalid_types*.)
1335+
invalid_types : tuple, optional
1336+
A list of types that determine which children will *not* be
1337+
returned by this sublist. If specified, then Artists in the
1338+
sublist will never be an instance of these types. Otherwise, no
1339+
types will be excluded.
1340+
"""
1341+
self._axes = axes
1342+
self._prop_name = prop_name
1343+
self._add_name = add_name
1344+
self._type_check = lambda artist: (
1345+
(not valid_types or isinstance(artist, valid_types)) and
1346+
(not invalid_types or not isinstance(artist, invalid_types))
1347+
)
1348+
1349+
def __repr__(self):
1350+
return f'<Axes.ArtistList of {self._prop_name}>'
1351+
1352+
def __len__(self):
1353+
return sum(self._type_check(artist)
1354+
for artist in self._axes._children)
1355+
1356+
def __getitem__(self, key):
1357+
return [artist
1358+
for artist in self._axes._children
1359+
if self._type_check(artist)][key]
1360+
1361+
def insert(self, index, item):
1362+
_api.warn_deprecated(
1363+
'3.5',
1364+
name=f'modification of the Axes.{self._prop_name}',
1365+
obj_type='property',
1366+
alternative=f'Axes.{self._add_name}')
1367+
try:
1368+
index = self._axes._children.index(self[index])
1369+
except IndexError:
1370+
index = None
1371+
getattr(self._axes, self._add_name)(item)
1372+
if index is not None:
1373+
# Move new item to the specified index, if there's something to
1374+
# put it before.
1375+
self._axes._children[index:index] = self._axes._children[-1:]
1376+
del self._axes._children[-1]
1377+
1378+
def __setitem__(self, key, item):
1379+
_api.warn_deprecated(
1380+
'3.5',
1381+
name=f'modification of the Axes.{self._prop_name}',
1382+
obj_type='property',
1383+
alternative=f'Artist.remove() and Axes.f{self._add_name}')
1384+
del self[key]
1385+
if isinstance(key, slice):
1386+
key = key.start
1387+
if not np.iterable(item):
1388+
self.insert(key, item)
1389+
return
1390+
1391+
try:
1392+
index = self._axes._children.index(self[key])
1393+
except IndexError:
1394+
index = None
1395+
for i, artist in enumerate(item):
1396+
getattr(self._axes, self._add_name)(artist)
1397+
if index is not None:
1398+
# Move new items to the specified index, if there's something
1399+
# to put it before.
1400+
i = -(i + 1)
1401+
self._axes._children[index:index] = self._axes._children[i:]
1402+
del self._axes._children[i:]
1403+
1404+
def __delitem__(self, key):
1405+
_api.warn_deprecated(
1406+
'3.5',
1407+
name=f'modification of the Axes.{self._prop_name}',
1408+
obj_type='property',
1409+
alternative='Artist.remove()')
1410+
if isinstance(key, slice):
1411+
for artist in self[key]:
1412+
artist.remove()
1413+
else:
1414+
self[key].remove()
1415+
13071416
@property
13081417
def artists(self):
1309-
return tuple(
1310-
a for a in self._children
1311-
if not isinstance(a, (
1312-
mcoll.Collection, mimage.AxesImage, mlines.Line2D,
1313-
mpatches.Patch, mtable.Table, mtext.Text)))
1418+
return self.ArtistList(self, 'artists', 'add_artist', invalid_types=(
1419+
mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch,
1420+
mtable.Table, mtext.Text))
13141421

13151422
@property
13161423
def collections(self):
1317-
return tuple(a for a in self._children
1318-
if isinstance(a, mcoll.Collection))
1424+
return self.ArtistList(self, 'collections', 'add_collection',
1425+
valid_types=mcoll.Collection)
13191426

13201427
@property
13211428
def images(self):
1322-
return tuple(a for a in self._children
1323-
if isinstance(a, mimage.AxesImage))
1429+
return self.ArtistList(self, 'images', 'add_image',
1430+
valid_types=mimage.AxesImage)
13241431

13251432
@property
13261433
def lines(self):
1327-
return tuple(a for a in self._children if isinstance(a, mlines.Line2D))
1434+
return self.ArtistList(self, 'lines', 'add_line',
1435+
valid_types=mlines.Line2D)
13281436

13291437
@property
13301438
def patches(self):
1331-
return tuple(a for a in self._children
1332-
if isinstance(a, mpatches.Patch))
1439+
return self.ArtistList(self, 'patches', 'add_patch',
1440+
valid_types=mpatches.Patch)
13331441

13341442
@property
13351443
def tables(self):
1336-
return tuple(a for a in self._children if isinstance(a, mtable.Table))
1444+
return self.ArtistList(self, 'tables', 'add_table',
1445+
valid_types=mtable.Table)
13371446

13381447
@property
13391448
def texts(self):
1340-
return tuple(a for a in self._children if isinstance(a, mtext.Text))
1449+
return self.ArtistList(self, 'texts', 'add_text',
1450+
valid_types=mtext.Text)
13411451

13421452
def clear(self):
13431453
"""Clear the axes."""

lib/matplotlib/tests/test_axes.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7029,3 +7029,78 @@ def test_warn_ignored_scatter_kwargs():
70297029
c = plt.scatter(
70307030
[0], [0], marker="+", s=500, facecolor="r", edgecolor="b"
70317031
)
7032+
7033+
7034+
def test_artist_sublists():
7035+
fig, ax = plt.subplots()
7036+
lines = [ax.plot(np.arange(i, i + 5))[0] for i in range(6)]
7037+
col = ax.scatter(np.arange(5), np.arange(5))
7038+
im = ax.imshow(np.zeros((5, 5)))
7039+
patch = ax.add_patch(mpatches.Rectangle((0, 0), 5, 5))
7040+
text = ax.text(0, 0, 'foo')
7041+
7042+
# Get items, which should not be mixed.
7043+
assert list(ax.collections) == [col]
7044+
assert list(ax.images) == [im]
7045+
assert list(ax.lines) == lines
7046+
assert list(ax.patches) == [patch]
7047+
assert not ax.tables
7048+
assert list(ax.texts) == [text]
7049+
7050+
# Get items should work like lists/tuple.
7051+
assert ax.lines[0] is lines[0]
7052+
assert ax.lines[-1] is lines[-1]
7053+
with pytest.raises(IndexError, match='out of range'):
7054+
ax.lines[len(lines) + 1]
7055+
7056+
# Deleting items (multiple or single) should warn.
7057+
with pytest.warns(MatplotlibDeprecationWarning,
7058+
match='modification of the Axes.lines property'):
7059+
del ax.lines[-1]
7060+
with pytest.warns(MatplotlibDeprecationWarning,
7061+
match='modification of the Axes.lines property'):
7062+
del ax.lines[-1:]
7063+
with pytest.warns(MatplotlibDeprecationWarning,
7064+
match='modification of the Axes.lines property'):
7065+
del ax.lines[1:]
7066+
with pytest.warns(MatplotlibDeprecationWarning,
7067+
match='modification of the Axes.lines property'):
7068+
del ax.lines[0]
7069+
7070+
# Lists should be empty after removing items.
7071+
col.remove()
7072+
assert not ax.collections
7073+
im.remove()
7074+
assert not ax.images
7075+
patch.remove()
7076+
assert not ax.patches
7077+
text.remove()
7078+
assert not ax.texts
7079+
7080+
# Everything else should remain empty.
7081+
assert not ax.lines
7082+
assert not ax.tables
7083+
7084+
# Adding items should warn.
7085+
with pytest.warns(MatplotlibDeprecationWarning,
7086+
match='modification of the Axes.lines property'):
7087+
ax.lines.append(lines[-2])
7088+
assert list(ax.lines) == [lines[-2]]
7089+
with pytest.warns(MatplotlibDeprecationWarning,
7090+
match='modification of the Axes.lines property'):
7091+
ax.lines.append(lines[-1])
7092+
assert list(ax.lines) == lines[-2:]
7093+
with pytest.warns(MatplotlibDeprecationWarning,
7094+
match='modification of the Axes.lines property'):
7095+
ax.lines.insert(-2, lines[0])
7096+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
7097+
7098+
# Modifying items should warn.
7099+
with pytest.warns(MatplotlibDeprecationWarning,
7100+
match='modification of the Axes.lines property'):
7101+
ax.lines[0] = lines[0]
7102+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
7103+
with pytest.warns(MatplotlibDeprecationWarning,
7104+
match='modification of the Axes.lines property'):
7105+
ax.lines[1:1] = lines[1:-2]
7106+
assert list(ax.lines) == lines

0 commit comments

Comments
 (0)