Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit451ca5a

Browse files
committed
META: aliasable _enums and some LineStyle bugfixes
1 parent8175daa commit451ca5a

File tree

3 files changed

+164
-110
lines changed

3 files changed

+164
-110
lines changed

‎lib/matplotlib/_api/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,7 @@ def warn_external(message, category=None):
211211
break
212212
frame=frame.f_back
213213
warnings.warn(message,category,stacklevel)
214+
215+
216+
defis_string_like(x):
217+
returnisinstance(x, (str,bytes,bytearray))

‎lib/matplotlib/_enums.py

Lines changed: 156 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,81 @@
1010
they define.
1111
"""
1212

13-
fromenumimportEnum,auto
14-
fromnumbersimportNumber
13+
fromenumimport_EnumDict,EnumMeta,Enum,auto
14+
15+
importnumpyasnp
1516

1617
frommatplotlibimport_api,docstring
1718

1819

19-
class_AutoStringNameEnum(Enum):
20-
"""Automate the ``name = 'name'`` part of making a (str, Enum)."""
20+
class_AliasableStrEnumDict(_EnumDict):
21+
"""Helper for `_AliasableEnumMeta`."""
22+
def__init__(self):
23+
super().__init__()
24+
self._aliases= {}
25+
# adopt the Python 3.10 convention of "auto()" simply using the name of
26+
# the attribute: https://bugs.python.org/issue42385
27+
# this can be removed once we no longer support Python 3.9
28+
self._generate_next_value \
29+
=lambdaname,start,count,last_values:name
30+
31+
def__setitem__(self,key,value):
32+
# if a class attribute with this name has already been created,
33+
# register this as an "alias"
34+
ifkeyinself:
35+
self._aliases[value]=self[key]
36+
else:
37+
super().__setitem__(key,value)
2138

22-
def_generate_next_value_(name,start,count,last_values):
23-
returnname
39+
40+
class_AliasableEnumMeta(EnumMeta):
41+
"""
42+
Allow Enums to have multiple "values" which are equivalent.
43+
44+
For a discussion of several approaches to "value aliasing", see
45+
https://stackoverflow.com/questions/24105268/is-it-possible-to-override-new-in-an-enum-to-parse-strings-to-an-instance
46+
"""
47+
@classmethod
48+
def__prepare__(metacls,cls,bases):
49+
# a custom dict (_EnumDict) is used when handing the __prepared__
50+
# class's namespace to EnumMeta.__new__. This way, when non-dunder,
51+
# non-descriptor class-level variables are added to the class namespace
52+
# during class-body execution, their values can be replaced with the
53+
# singletons that will later be returned by Enum.__call__.
54+
55+
# We over-ride this dict to prevent _EnumDict's internal checks from
56+
# throwing an error whenever preventing the same name is inserted
57+
# twice. Instead, we add that name to a _aliases dict that can be
58+
# used to look up the correct singleton later.
59+
return_AliasableStrEnumDict()
60+
61+
def__new__(metacls,cls,bases,classdict):
62+
# add our _aliases dict to the newly created class, so that it
63+
# can be used by __call__.
64+
enum_class=super().__new__(metacls,cls,bases,classdict)
65+
enum_class._aliases_=classdict._aliases
66+
returnenum_class
67+
68+
def__call__(cls,value,*args,**kw):
69+
# convert the value to the "default" if it is an alias, and then simply
70+
# forward to Enum
71+
ifvaluenotincls._value2member_map_andvalueincls._aliases_:
72+
value=cls._aliases_[value]
73+
returnsuper().__call__(value,*args,**kw)
74+
75+
76+
class_AliasableStringNameEnum(Enum,metaclass=_AliasableEnumMeta):
77+
"""
78+
Convenience mix-in for easier construction of string enums.
79+
80+
Automates the ``name = 'name'`` part of making a (str, Enum), using the
81+
semantics that have now been adopted as part of Python 3.10:
82+
(bugs.python.org/issue42385).
83+
84+
In addition, allow multiple strings to be synonyms for the same underlying
85+
Enum value. This allows us to easily have things like ``LineStyle('--') ==
86+
LineStyle('dashed')`` work as expected.
87+
"""
2488

2589
def__hash__(self):
2690
returnstr(self).__hash__()
@@ -43,7 +107,7 @@ def _deprecate_case_insensitive_join_cap(s):
43107
returns_low
44108

45109

46-
classJoinStyle(str,_AutoStringNameEnum):
110+
classJoinStyle(str,_AliasableStringNameEnum):
47111
"""
48112
Define how the connection between two line segments is drawn.
49113
@@ -139,7 +203,7 @@ def plot_angle(ax, x, y, angle, style):
139203
+"}"
140204

141205

142-
classCapStyle(str,_AutoStringNameEnum):
206+
classCapStyle(str,_AliasableStringNameEnum):
143207
r"""
144208
Define how the two endpoints (caps) of an unclosed line are drawn.
145209
@@ -211,7 +275,7 @@ def demo():
211275

212276

213277
#: Maps short codes for line style to their full name used by backends.
214-
_ls_mapper= {'':'None',' ':'None','none':'None',
278+
_ls_mapper= {'':'none',' ':'none','none':'none',
215279
'-':'solid','--':'dashed','-.':'dashdot',':':'dotted'}
216280
_deprecated_lineStyles= {
217281
'-':'_draw_solid',
@@ -224,7 +288,37 @@ def demo():
224288
}
225289

226290

227-
classNamedLineStyle(str,_AutoStringNameEnum):
291+
def_validate_onoffseq(x):
292+
"""Raise a helpful error message for malformed onoffseq."""
293+
err='In a custom LineStyle (offset, onoffseq), the onoffseq must '
294+
if_api.is_string_like(x):
295+
raiseValueError(err+'not be a string.')
296+
ifnotnp.iterable(x):
297+
raiseValueError(err+'be iterable.')
298+
ifnotlen(x)%2==0:
299+
raiseValueError(err+'be of even length.')
300+
ifnotnp.all(x>0):
301+
raiseValueError(err+'have strictly positive, numerical elements.')
302+
303+
304+
class_NamedLineStyle(_AliasableStringNameEnum):
305+
"""A standardized way to refer to each named LineStyle internally."""
306+
solid=auto()
307+
solid='-'
308+
dashed=auto()
309+
dashed='--'
310+
dotted=auto()
311+
dotted=':'
312+
dashdot=auto()
313+
dashdot='-.'
314+
none=auto()
315+
none='None'
316+
none=' '
317+
none=''
318+
custom=auto()
319+
320+
321+
classLineStyle:
228322
"""
229323
Describe if the line is solid or dashed, and the dash pattern, if any.
230324
@@ -239,7 +333,7 @@ class NamedLineStyle(str, _AutoStringNameEnum):
239333
``'--'`` or ``'dashed'`` dashed line
240334
``'-.'`` or ``'dashdot'`` dash-dotted line
241335
``':'`` or ``'dotted'`` dotted line
242-
``'None'`` or ``' '`` or ``''`` draw nothing
336+
``'none'`` or ``' '`` or ``''`` draw nothing
243337
=============================== =================
244338
245339
However, for more fine-grained control, one can directly specify the
@@ -249,18 +343,17 @@ class NamedLineStyle(str, _AutoStringNameEnum):
249343
250344
where ``onoffseq`` is an even length tuple specifying the lengths of each
251345
subsequent dash and space, and ``offset`` controls at which point in this
252-
pattern the start of the line will begin (to allow you to e.g. prevent
253-
corners from happening to land in between dashes).
254-
255-
For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
256-
dashes separated by 2 point spaces.
346+
pattern the start of the line will begin (allowing you to, for example,
347+
prevent a sharp corner landing in between dashes and therefore not being
348+
drawn).
257349
258-
Setting ``onoffseq`` to ``None`` results in a solid *LineStyle*.
350+
For example, the ``onoffseq`` (5, 2, 1, 2) describes a sequence of 5 point
351+
and 1 point dashes separated by 2 point spaces.
259352
260353
The default dashing patterns described in the table above are themselves
261-
all described in this notation, and can therefore be customized by editing
262-
the appropriate ``lines.*_pattern`` *rc* parameter, as described in
263-
:doc:`/tutorials/introductory/customizing`.
354+
defined under the hood using an offset and an onoffseq, and can therefore
355+
be customized by editingthe appropriate ``lines.*_pattern`` *rc*
356+
parameter, as described in:doc:`/tutorials/introductory/customizing`.
264357
265358
.. plot::
266359
:alt: Demo of possible LineStyle's.
@@ -271,22 +364,15 @@ class NamedLineStyle(str, _AutoStringNameEnum):
271364
.. note::
272365
273366
In addition to directly taking a ``linestyle`` argument,
274-
`~.lines.Line2D` exposes a ``~.lines.Line2D.set_dashes`` method that
275-
can be used to create a new *LineStyle* by providing just the
276-
``onoffseq``, but does not let you customize the offset. This method is
277-
called when using the keyword *dashes* to the cycler , as shown in
278-
:doc:`property_cycle </tutorials/intermediate/color_cycle>`.
367+
`~.lines.Line2D` exposes a ``~.lines.Line2D.set_dashes`` method (and
368+
the :doc:`property_cycle </tutorials/intermediate/color_cycle>` has a
369+
*dashes* keyword) that can be used to create a new *LineStyle* by
370+
providing just the ``onoffseq``, but does not let you customize the
371+
offset. This method simply sets the underlying linestyle, and is only
372+
kept for backwards compatibility.
279373
"""
280-
solid=auto()
281-
dashed=auto()
282-
dotted=auto()
283-
dashdot=auto()
284-
none=auto()
285-
custom=auto()
286374

287-
classLineStyle(str):
288-
289-
def__init__(self,ls,scale=1):
375+
def__init__(self,ls):
290376
"""
291377
Parameters
292378
----------
@@ -301,56 +387,58 @@ def __init__(self, ls, scale=1):
301387
"""
302388

303389
self._linestyle_spec=ls
304-
ifisinstance(ls,str):
305-
iflsin [' ','','None']:
306-
ls='none'
307-
iflsin_ls_mapper:
308-
ls=_ls_mapper[ls]
309-
Enum.__init__(self)
310-
offset,onoffseq=None,None
390+
if_api.is_string_like(ls):
391+
self._name=_NamedLineStyle(ls)
392+
self._offset,self._onoffseq=None,None
311393
else:
394+
self._name=_NamedLineStyle('custom')
312395
try:
313-
offset,onoffseq=ls
396+
self._offset,self._onoffseq=ls
314397
exceptValueError:# not enough/too many values to unpack
315-
raiseValueError('LineStyle should be a string or a 2-tuple, '
316-
'instead received: '+str(ls))
317-
ifoffsetisNone:
398+
raiseValueError('Custom LineStyle must be a 2-tuple (offset, '
399+
'onoffseq), instead received: '+str(ls))
400+
_validate_onoffseq(self._onoffseq)
401+
ifself._offsetisNone:
318402
_api.warn_deprecated(
319403
"3.3",message="Passing the dash offset as None is deprecated "
320404
"since %(since)s and support for it will be removed "
321405
"%(removal)s; pass it as zero instead.")
322-
offset=0
406+
self._offset=0
323407

324-
ifonoffseqisnotNone:
325-
# normalize offset to be positive and shorter than the dash cycle
326-
dsum=sum(onoffseq)
327-
ifdsum:
328-
offset%=dsum
329-
iflen(onoffseq)%2!=0:
330-
raiseValueError('LineStyle onoffseq must be of even length.')
331-
ifnotall(isinstance(elem,Number)foreleminonoffseq):
332-
raiseValueError('LineStyle onoffseq must be list of floats.')
333-
self._us_offset=offset
334-
self._us_onoffseq=onoffseq
408+
def__eq__(self,other):
409+
ifnotisinstance(other,LineStyle):
410+
other=LineStyle(other)
411+
returnself.get_dashes()==other.get_dashes()
335412

336413
def__hash__(self):
337-
ifself==LineStyle.custom:
338-
return (self._us_offset,tuple(self._us_onoffseq)).__hash__()
339-
return_AutoStringNameEnum.__hash__(self)
414+
ifself._name==LineStyle.custom:
415+
return (self._offset,tuple(self._onoffseq)).__hash__()
416+
return_AliasableStringNameEnum.__hash__(self._name)
340417

418+
@staticmethod
419+
def_normalize_offset(offset,onoffseq):
420+
"""Normalize offset to be positive and shorter than the dash cycle."""
421+
dsum=sum(onoffseq)
422+
ifdsum:
423+
offset%=dsum
424+
returnoffset
425+
426+
defis_dashed(self):
427+
offset,onoffseq=self.get_dashes()
428+
returnnp.isclose(np.sum(onoffseq),0)
341429

342430
defget_dashes(self,lw=1):
343431
"""
344432
Get the (scaled) dash sequence for this `.LineStyle`.
345433
"""
346-
#deferlookupuntildraw time
347-
ifself._us_offsetisNoneorself._us_onoffseqisNone:
348-
self._us_offset,self._us_onoffseq=\
349-
LineStyle._get_dash_pattern(self.name)
350-
# normalizeoffset to be positive and shorter than the dash cycle
351-
dsum=sum(self._us_onoffseq)
352-
self._us_offset%=dsum
353-
returnself._scale_dashes(self._us_offset,self._us_onoffseq,lw)
434+
#named linestylelookuphappens atdraw time (here)
435+
ifself._onoffseqisNone:
436+
offset,onoffseq=LineStyle._get_dash_pattern(self._name)
437+
else:
438+
offset,onoff_seq=self._offset,self._onoffseq
439+
# force 0 <= offset < dash cycle length
440+
offset=LineStyle._normalize_offset(offset,onoffseq)
441+
returnself._scale_dashes(offset,onoffseq,lw)
354442

355443
@staticmethod
356444
def_scale_dashes(offset,dashes,lw):
@@ -462,6 +550,5 @@ def plot_linestyles(ax, linestyles, title):
462550
plt.tight_layout()
463551
plt.show()
464552

465-
466553
LineStyle._ls_mapper=_ls_mapper
467554
LineStyle._deprecated_lineStyles=_deprecated_lineStyles

‎lib/matplotlib/rcsetup.py

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
importast
1717
fromfunctoolsimportlru_cache,reduce
1818
importlogging
19-
fromnumbersimportNumber
2019
importoperator
2120
importre
2221

@@ -527,8 +526,6 @@ def validate_ps_distiller(s):
527526
returnValidateInStrings('ps.usedistiller', ['ghostscript','xpdf'])(s)
528527

529528

530-
# A validator dedicated to the named line styles, based on the items in
531-
# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
532529
def_validate_linestyle(ls):
533530
"""
534531
A validator for all possible line styles, the named ones *and*
@@ -539,48 +536,14 @@ def _validate_linestyle(ls):
539536
ls=ast.literal_eval(ls)# Parsing matplotlibrc.
540537
except (SyntaxError,ValueError):
541538
pass# Will error with the ValueError at the end.
542-
543-
<<<<<<<HEAD
544-
def_is_iterable_not_string_like(x):
545-
# Explicitly exclude bytes/bytearrays so that they are not
546-
# nonsensically interpreted as sequences of numbers (codepoints).
547-
returnnp.iterable(x)andnotisinstance(x, (str,bytes,bytearray))
548-
549-
# (offset, (on, off, on, off, ...))
550-
if (_is_iterable_not_string_like(ls)
551-
andlen(ls)==2
552-
andisinstance(ls[0], (type(None),Number))
553-
and_is_iterable_not_string_like(ls[1])
554-
andlen(ls[1])%2==0
555-
andall(isinstance(elem,Number)foreleminls[1])):
556-
ifls[0]isNone:
557-
_api.warn_deprecated(
558-
"3.3",message="Passing the dash offset as None is deprecated "
559-
"since %(since)s and support for it will be removed "
560-
"%(removal)s; pass it as zero instead.")
561-
ls= (0,ls[1])
562-
returnls
563-
# For backcompat: (on, off, on, off, ...); the offset is implicitly None.
564-
if (_is_iterable_not_string_like(ls)
565-
andlen(ls)%2==0
566-
andall(isinstance(elem,Number)foreleminls)):
567-
return (0,ls)
568-
raiseValueError(f"linestyle{ls!r} is not a valid on-off ink sequence.")
569-
=======
570539
try:
571540
LineStyle(ls)
572541
exceptValueErrorase:
573-
# For backcompat, only in rc, we allow the user to pash only the
574-
# onoffseq, and set the offset implicitly to 0
575-
if (np.iterable(ls)andnotisinstance(ls, (str,bytes,bytearray))
576-
andlen(ls)%2==0
577-
andall(isinstance(elem,Number)foreleminls)):
578-
try:
579-
LineStyle((0,ls))
580-
exceptValueError:
581-
raisee
542+
try:
543+
LineStyle((0,ls))
544+
exceptValueError:
545+
raisee
582546
returnls
583-
>>>>>>>b2d2793cc...GSOD:LineStyleclass
584547

585548

586549
validate_fillstyle=ValidateInStrings(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp