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

Skip to content

Commit acbac82

Browse files
committed
Allow Paths to be marked as readonly, mark all of the singletons as readonly, and deepcopy those Paths in tests that need to modify the path.
1 parent d5b078b commit acbac82

6 files changed

Lines changed: 110 additions & 20 deletions

File tree

doc/api/api_changes.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ For new features that were added to matplotlib, please see
1515
Changes in 1.3.x
1616
================
1717

18+
* `Path` objects can now be marked as `readonly` by passing
19+
`readonly=True` to its constructor. The built-in path singletons,
20+
obtained through `Path.unit*` class methods return readonly paths.
21+
If you have code that modified these, you will need to make a
22+
deepcopy first, using either::
23+
24+
import copy
25+
path = copy.deepcopy(Path.unit_circle())
26+
27+
# or
28+
29+
path = Path.unit_circle().deepcopy()
30+
31+
Deep copying a `Path` always creates an editable (i.e. non-readonly)
32+
`Path`.
33+
1834
* The `font.*` rcParams now affect only text objects created after the
1935
rcParam has been set, and will not retroactively affect already
2036
existing text objects. This brings their behavior in line with most

lib/matplotlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,7 @@ def tk_window_focus():
11931193
'matplotlib.tests.test_mathtext',
11941194
'matplotlib.tests.test_mlab',
11951195
'matplotlib.tests.test_patches',
1196+
'matplotlib.tests.test_path',
11961197
'matplotlib.tests.test_patheffects',
11971198
'matplotlib.tests.test_pickle',
11981199
'matplotlib.tests.test_rcparams',

lib/matplotlib/path.py

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ class Path(object):
8484

8585
code_type = np.uint8
8686

87-
def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
87+
def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False,
88+
readonly=False):
8889
"""
8990
Create a new path with the given vertices and codes.
9091
@@ -109,6 +110,8 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
109110
such as Polar, that this path should be linearly interpolated
110111
immediately before drawing. This attribute is primarily an
111112
implementation detail and is not intended for public use.
113+
114+
*readonly*, when True, makes the path immutable.
112115
"""
113116
if ma.isMaskedArray(vertices):
114117
vertices = vertices.astype(np.float_).filled(np.nan)
@@ -130,14 +133,77 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
130133
assert vertices.ndim == 2
131134
assert vertices.shape[1] == 2
132135

133-
self.should_simplify = (rcParams['path.simplify'] and
134-
(len(vertices) >= 128 and
135-
(codes is None or np.all(codes <= Path.LINETO))))
136-
self.simplify_threshold = rcParams['path.simplify_threshold']
137-
self.has_nonfinite = not np.isfinite(vertices).all()
138-
self.codes = codes
139-
self.vertices = vertices
136+
self._vertices = vertices
137+
self._codes = codes
140138
self._interpolation_steps = _interpolation_steps
139+
self._update_values()
140+
141+
if readonly:
142+
self._vertices.flags.writeable = False
143+
if self._codes is not None:
144+
self._codes.flags.writeable = False
145+
self._readonly = True
146+
else:
147+
self._readonly = False
148+
149+
def _update_values(self):
150+
self._should_simplify = (
151+
rcParams['path.simplify'] and
152+
(len(self._vertices) >= 128 and
153+
(self._codes is None or np.all(self._codes <= Path.LINETO))))
154+
self._simplify_threshold = rcParams['path.simplify_threshold']
155+
self._has_nonfinite = not np.isfinite(self._vertices).all()
156+
157+
@property
158+
def vertices(self):
159+
return self._vertices
160+
161+
@vertices.setter
162+
def vertices(self, vertices):
163+
if self._readonly:
164+
raise AttributeError("Can't set vertices on a readonly Path")
165+
self._vertices = vertices
166+
self._update_values()
167+
168+
@property
169+
def codes(self):
170+
return self._codes
171+
172+
@codes.setter
173+
def codes(self, codes):
174+
if self._readonly:
175+
raise AttributeError("Can't set codes on a readonly Path")
176+
self._codes = codes
177+
self._update_values()
178+
179+
@property
180+
def simplify_threshold(self):
181+
return self._simplify_threshold
182+
183+
@property
184+
def has_nonfinite(self):
185+
return self._has_nonfinite
186+
187+
@property
188+
def should_simplify(self):
189+
return self._should_simplify
190+
191+
@property
192+
def readonly(self):
193+
return self._readonly
194+
195+
def __copy__(self):
196+
import copy
197+
return copy.copy(self)
198+
199+
copy = __copy__
200+
201+
def __deepcopy__(self):
202+
return self.__class__(
203+
self.vertices.copy(), self.codes.copy(),
204+
_interpolation_steps=self._interpolation_steps)
205+
206+
deepcopy = __deepcopy__
141207

142208
@classmethod
143209
def make_compound_path_from_polys(cls, XY):
@@ -420,7 +486,8 @@ def unit_rectangle(cls):
420486
if cls._unit_rectangle is None:
421487
cls._unit_rectangle = \
422488
cls([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]],
423-
[cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO, cls.CLOSEPOLY])
489+
[cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO, cls.CLOSEPOLY],
490+
readonly=True)
424491
return cls._unit_rectangle
425492

426493
_unit_regular_polygons = WeakValueDictionary()
@@ -447,7 +514,7 @@ def unit_regular_polygon(cls, numVertices):
447514
codes[0] = cls.MOVETO
448515
codes[1:-1] = cls.LINETO
449516
codes[-1] = cls.CLOSEPOLY
450-
path = cls(verts, codes)
517+
path = cls(verts, codes, readonly=True)
451518
if numVertices <= 16:
452519
cls._unit_regular_polygons[numVertices] = path
453520
return path
@@ -478,7 +545,7 @@ def unit_regular_star(cls, numVertices, innerCircle=0.5):
478545
codes[0] = cls.MOVETO
479546
codes[1:-1] = cls.LINETO
480547
codes[-1] = cls.CLOSEPOLY
481-
path = cls(verts, codes)
548+
path = cls(verts, codes, readonly=True)
482549
if numVertices <= 16:
483550
cls._unit_regular_polygons[(numVertices, innerCircle)] = path
484551
return path
@@ -552,7 +619,7 @@ def unit_circle(cls):
552619
codes[0] = cls.MOVETO
553620
codes[-1] = cls.CLOSEPOLY
554621

555-
cls._unit_circle = cls(vertices, codes)
622+
cls._unit_circle = cls(vertices, codes, readonly=True)
556623
return cls._unit_circle
557624

558625
_unit_circle_righthalf = None
@@ -600,7 +667,7 @@ def unit_circle_righthalf(cls):
600667
codes[0] = cls.MOVETO
601668
codes[-1] = cls.CLOSEPOLY
602669

603-
cls._unit_circle_righthalf = cls(vertices, codes)
670+
cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
604671
return cls._unit_circle_righthalf
605672

606673
@classmethod
@@ -679,7 +746,7 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False):
679746
vertices[vertex_offset+2:end:3, 0] = xB
680747
vertices[vertex_offset+2:end:3, 1] = yB
681748

682-
return cls(vertices, codes)
749+
return cls(vertices, codes, readonly=True)
683750

684751
@classmethod
685752
def wedge(cls, theta1, theta2, n=None):

lib/matplotlib/tests/test_bbox_tight.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ def test_bbox_inches_tight_clipping():
7474
transform=ax.transData,
7575
facecolor='blue', alpha=0.5)
7676

77-
path = mpath.Path.unit_regular_star(5)
78-
path.vertices = path.vertices.copy()
77+
path = mpath.Path.unit_regular_star(5).deepcopy()
7978
path.vertices *= 0.25
8079
patch.set_clip_path(path, transform=ax.transAxes)
8180
plt.gcf().artists.append(patch)

lib/matplotlib/tests/test_patches.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,11 @@ def test_clip_to_bbox():
8383
ax.set_xlim([-18, 20])
8484
ax.set_ylim([-150, 100])
8585

86-
star = mpath.Path.unit_regular_star(8)
87-
path = mpath.Path(star.vertices.copy(), star.codes)
86+
path = mpath.Path.unit_regular_star(8).deepcopy()
8887
path.vertices *= [10, 100]
8988
path.vertices -= [5, 25]
9089

91-
circle = mpath.Path.unit_circle()
92-
path2 = mpath.Path(circle.vertices.copy(), circle.codes)
90+
path2 = mpath.Path.unit_circle().deepcopy()
9391
path2.vertices *= [10, 100]
9492
path2.vertices += [10, -25]
9593

lib/matplotlib/tests/test_path.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from matplotlib.path import Path
2+
from nose.tools import assert_raises
3+
4+
def test_readonly_path():
5+
def readonly():
6+
path = Path.unit_circle()
7+
path.vertices = path.vertices * 2.0
8+
9+
assert_raises(AttributeError, readonly)

0 commit comments

Comments
 (0)