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

Skip to content

Commit f280c4e

Browse files
committed
Add warn-on-modification wrappers around Axes.* lists.
1 parent b52913a commit f280c4e

File tree

2 files changed

+198
-14
lines changed

2 files changed

+198
-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 inspect
45
import itertools
@@ -1221,40 +1222,149 @@ def cla(self):
12211222

12221223
self.stale = True
12231224

1225+
class ArtistList(MutableSequence):
1226+
"""
1227+
A sublist of Axes children based on their type.
1228+
1229+
This exists solely to warn on modification. In the future, the
1230+
type-specific children sublists will be immutable tuples.
1231+
"""
1232+
def __init__(self, axes, prop_name, add_name,
1233+
valid_types=None, invalid_types=None):
1234+
"""
1235+
Parameters
1236+
----------
1237+
axes : .axes.Axes
1238+
The Axes from which this sublist will pull the children
1239+
Artists.
1240+
prop_name : str
1241+
The property name used to access this sublist from the Axes;
1242+
used to generate deprecation warnings.
1243+
add_name : str
1244+
The method name used to add Artists of this sublist's type to
1245+
the Axes; used to generate deprecation warnings.
1246+
valid_types : list of type, optional
1247+
A list of types that determine which children will be returned
1248+
by this sublist. If specified, then the Artists in the sublist
1249+
must be instances of any of these types. If unspecified, then
1250+
any type of Artist is valid (unless limited by
1251+
*invalid_types*.)
1252+
invalid_types : tuple, optional
1253+
A list of types that determine which children will *not* be
1254+
returned by this sublist. If specified, then Artists in the
1255+
sublist will never be an instance of these types. Otherwise, no
1256+
types will be excluded.
1257+
"""
1258+
self._axes = axes
1259+
self._prop_name = prop_name
1260+
self._add_name = add_name
1261+
self._type_check = lambda artist: (
1262+
(not valid_types or isinstance(artist, valid_types)) and
1263+
(not invalid_types or not isinstance(artist, invalid_types))
1264+
)
1265+
1266+
def __repr__(self):
1267+
return f'<Axes.ArtistList of {self._prop_name}>'
1268+
1269+
def __len__(self):
1270+
return sum(self._type_check(artist)
1271+
for artist in self._axes._children)
1272+
1273+
def __getitem__(self, key):
1274+
return [artist
1275+
for artist in self._axes._children
1276+
if self._type_check(artist)][key]
1277+
1278+
def insert(self, index, item):
1279+
cbook.warn_deprecated(
1280+
'3.4',
1281+
name=f'modification of the Axes.{self._prop_name}',
1282+
obj_type='property',
1283+
alternative=f'Axes.{self._add_name}')
1284+
try:
1285+
index = self._axes._children.index(self[index])
1286+
except IndexError:
1287+
index = None
1288+
getattr(self._axes, self._add_name)(item)
1289+
if index is not None:
1290+
# Move new item to the specified index, if there's something to
1291+
# put it before.
1292+
self._axes._children[index:index] = self._axes._children[-1:]
1293+
del self._axes._children[-1]
1294+
1295+
def __setitem__(self, key, item):
1296+
cbook.warn_deprecated(
1297+
'3.4',
1298+
name=f'modification of the Axes.{self._prop_name}',
1299+
obj_type='property',
1300+
alternative=f'Artist.remove() and Axes.f{self._add_name}')
1301+
del self[key]
1302+
if isinstance(key, slice):
1303+
key = key.start
1304+
if not np.iterable(item):
1305+
self.insert(key, item)
1306+
return
1307+
1308+
try:
1309+
index = self._axes._children.index(self[key])
1310+
except IndexError:
1311+
index = None
1312+
for i, artist in enumerate(item):
1313+
getattr(self._axes, self._add_name)(artist)
1314+
if index is not None:
1315+
# Move new items to the specified index, if there's something
1316+
# to put it before.
1317+
i = -(i + 1)
1318+
self._axes._children[index:index] = self._axes._children[i:]
1319+
del self._axes._children[i:]
1320+
1321+
def __delitem__(self, key):
1322+
cbook.warn_deprecated(
1323+
'3.4',
1324+
name=f'modification of the Axes.{self._prop_name}',
1325+
obj_type='property',
1326+
alternative='Artist.remove()')
1327+
if isinstance(key, slice):
1328+
for artist in self[key]:
1329+
artist.remove()
1330+
else:
1331+
self[key].remove()
1332+
12241333
@property
12251334
def artists(self):
1226-
return tuple(
1227-
a for a in self._children
1228-
if not isinstance(a, (
1229-
mcoll.Collection, mimage.AxesImage, mlines.Line2D,
1230-
mpatches.Patch, mtable.Table, mtext.Text)))
1335+
return self.ArtistList(self, 'artists', 'add_artist', invalid_types=(
1336+
mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch,
1337+
mtable.Table, mtext.Text))
12311338

12321339
@property
12331340
def collections(self):
1234-
return tuple(a for a in self._children
1235-
if isinstance(a, mcoll.Collection))
1341+
return self.ArtistList(self, 'collections', 'add_collection',
1342+
valid_types=mcoll.Collection)
12361343

12371344
@property
12381345
def images(self):
1239-
return tuple(a for a in self._children
1240-
if isinstance(a, mimage.AxesImage))
1346+
return self.ArtistList(self, 'images', 'add_image',
1347+
valid_types=mimage.AxesImage)
12411348

12421349
@property
12431350
def lines(self):
1244-
return tuple(a for a in self._children if isinstance(a, mlines.Line2D))
1351+
return self.ArtistList(self, 'lines', 'add_line',
1352+
valid_types=mlines.Line2D)
12451353

12461354
@property
12471355
def patches(self):
1248-
return tuple(a for a in self._children
1249-
if isinstance(a, mpatches.Patch))
1356+
return self.ArtistList(self, 'patches', 'add_patch',
1357+
valid_types=mpatches.Patch)
12501358

12511359
@property
12521360
def tables(self):
1253-
return tuple(a for a in self._children if isinstance(a, mtable.Table))
1361+
return self.ArtistList(self, 'tables', 'add_table',
1362+
valid_types=mtable.Table)
12541363

12551364
@property
12561365
def texts(self):
1257-
return tuple(a for a in self._children if isinstance(a, mtext.Text))
1366+
return self.ArtistList(self, 'texts', 'add_text',
1367+
valid_types=mtext.Text)
12581368

12591369
def clear(self):
12601370
"""Clear the axes."""

lib/matplotlib/tests/test_axes.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6814,3 +6814,77 @@ def test_ylabel_ha_with_position(ha):
68146814
ax.set_ylabel("test", y=1, ha=ha)
68156815
ax.yaxis.set_label_position("right")
68166816
assert ax.yaxis.get_label().get_ha() == ha
6817+
6818+
6819+
def test_artist_sublists():
6820+
fig, ax = plt.subplots()
6821+
lines = [ax.plot(np.arange(i, i + 5))[0] for i in range(6)]
6822+
col = ax.scatter(np.arange(5), np.arange(5))
6823+
im = ax.imshow(np.zeros((5, 5)))
6824+
patch = ax.add_patch(mpatches.Rectangle((0, 0), 5, 5))
6825+
text = ax.text(0, 0, 'foo')
6826+
6827+
# Get items, which should not be mixed.
6828+
assert list(ax.collections) == [col]
6829+
assert list(ax.images) == [im]
6830+
assert list(ax.lines) == lines
6831+
assert list(ax.patches) == [patch]
6832+
assert not ax.tables
6833+
assert list(ax.texts) == [text]
6834+
6835+
# Get items should work like lists/tuple.
6836+
assert ax.lines[0] == lines[0]
6837+
assert ax.lines[-1] == lines[-1]
6838+
with pytest.raises(IndexError, match='out of range'):
6839+
ax.lines[len(lines) + 1]
6840+
6841+
# Deleting items (multiple or single) should warn.
6842+
with pytest.warns(MatplotlibDeprecationWarning,
6843+
match='modification of the Axes.lines property'):
6844+
del ax.lines[-1]
6845+
with pytest.warns(MatplotlibDeprecationWarning,
6846+
match='modification of the Axes.lines property'):
6847+
del ax.lines[-1:]
6848+
with pytest.warns(MatplotlibDeprecationWarning,
6849+
match='modification of the Axes.lines property'):
6850+
del ax.lines[1:]
6851+
with pytest.warns(MatplotlibDeprecationWarning,
6852+
match='modification of the Axes.lines property'):
6853+
del ax.lines[0]
6854+
6855+
col.remove()
6856+
im.remove()
6857+
patch.remove()
6858+
text.remove()
6859+
6860+
# Everything should be empty now.
6861+
assert not ax.collections
6862+
assert not ax.images
6863+
assert not ax.lines
6864+
assert not ax.patches
6865+
assert not ax.tables
6866+
assert not ax.texts
6867+
6868+
# Adding items should warn.
6869+
with pytest.warns(MatplotlibDeprecationWarning,
6870+
match='modification of the Axes.lines property'):
6871+
ax.lines.append(lines[-2])
6872+
assert list(ax.lines) == [lines[-2]]
6873+
with pytest.warns(MatplotlibDeprecationWarning,
6874+
match='modification of the Axes.lines property'):
6875+
ax.lines.append(lines[-1])
6876+
assert list(ax.lines) == lines[-2:]
6877+
with pytest.warns(MatplotlibDeprecationWarning,
6878+
match='modification of the Axes.lines property'):
6879+
ax.lines.insert(-2, lines[0])
6880+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
6881+
6882+
# Modifying items should warn.
6883+
with pytest.warns(MatplotlibDeprecationWarning,
6884+
match='modification of the Axes.lines property'):
6885+
ax.lines[0] = lines[0]
6886+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
6887+
with pytest.warns(MatplotlibDeprecationWarning,
6888+
match='modification of the Axes.lines property'):
6889+
ax.lines[1:1] = lines[1:-2]
6890+
assert list(ax.lines) == lines

0 commit comments

Comments
 (0)