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

Skip to content

Commit fa1decd

Browse files
committed
Automagically set the stacklevel on warnings.
There are many places in Matplotlib where it is impossible to set a static stacklevel on warnings that works in all cases, because a same function may either be called directly or via some wrapper (e.g. pyplot). Instead, compute the stacklevel by walking the stack. Given that warnings refer to conditions that should, well, be avoided, I believe we don't need to worry too much about the runtime cost. As an example, use this mechanism for the "ambiguous second argument to plot" warning. Now both ``` plt.gca().plot("x", "y", data={"x": 1, "y": 2}) ``` and ``` plt.plot("x", "y", data={"x": 1, "y": 2}) ``` emit a warning that refers to the relevant line in the user source, whereas previously the second snippet would refer to the pyplot wrapper, which is irrelevant to the user. Note that this only works from source, not from IPython, as the latter uses `exec` and AFAICT there is no value of stacklevel that correctly refers to the string being exec'd. Of course, the idea would be to ultimately use this throughout the codebase.
1 parent 1e6790f commit fa1decd

File tree

2 files changed

+22
-4
lines changed

2 files changed

+22
-4
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ def _plot_args_replacer(args, data):
6464
except ValueError:
6565
pass
6666
else:
67-
warnings.warn(
67+
cbook._warn_external(
6868
"Second argument {!r} is ambiguous: could be a color spec but "
6969
"is in data; using as data. Either rename the entry in data "
7070
"or use three arguments to plot.".format(args[1]),
71-
RuntimeWarning, stacklevel=3)
71+
RuntimeWarning)
7272
return ["x", "y"]
7373
elif len(args) == 3:
7474
return ["x", "y", "c"]

lib/matplotlib/cbook/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import glob
1515
import gzip
1616
import io
17-
from itertools import repeat
17+
import itertools
1818
import locale
1919
import numbers
2020
import operator
@@ -1254,7 +1254,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap):
12541254

12551255
ncols = len(X)
12561256
if labels is None:
1257-
labels = repeat(None)
1257+
labels = itertools.repeat(None)
12581258
elif len(labels) != ncols:
12591259
raise ValueError("Dimensions of labels and X must be compatible")
12601260

@@ -2032,3 +2032,21 @@ def _setattr_cm(obj, **kwargs):
20322032
delattr(obj, attr)
20332033
else:
20342034
setattr(obj, attr, orig)
2035+
2036+
2037+
def _warn_external(message, category=None):
2038+
"""
2039+
`warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib".
2040+
2041+
The original emitter of the warning can be obtained by patching this
2042+
function back to `warnings.warn`, i.e. ``cbook._warn_external =
2043+
warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``,
2044+
etc.).
2045+
"""
2046+
frame = sys._getframe()
2047+
for stacklevel in itertools.count(1):
2048+
if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.)",
2049+
frame.f_globals["__name__"]):
2050+
break
2051+
frame = frame.f_back
2052+
warnings.warn(message, category, stacklevel)

0 commit comments

Comments
 (0)