Skip to content

Python 3.9 upgrade #24980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ c1a33a481b9c2df605bcb9bef9c19fe65c3dac21

# chore: fix spelling errors
686c9e5a413e31c46bb049407d5eca285bcab76d

# chore: pyupgrade --py39-plus
4d306402bb66d6d4c694d8e3e14b91054417070e
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _check_dependencies():
if missing:
raise ImportError(
"The following dependencies are missing to build the "
"documentation: {}".format(", ".join(missing)))
f"documentation: {', '.join(missing)}")
if shutil.which('dot') is None:
raise OSError(
"No binary named dot - graphviz must be installed to build the "
Expand Down
2 changes: 1 addition & 1 deletion doc/sphinxext/gallery_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def __init__(self, src_dir):
def __call__(self, item):
"""Return a string determining the sort order."""
if item in self.ordered_list:
return "{:04d}".format(self.ordered_list.index(item))
return f"{self.ordered_list.index(item):04d}"
else:
# ensure not explicitly listed items come last.
return "zzz" + item
Expand Down
2 changes: 1 addition & 1 deletion doc/sphinxext/missing_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def record_missing_reference(app, record, node):
target = node["reftarget"]
location = get_location(node, app)

domain_type = "{}:{}".format(domain, typ)
domain_type = f"{domain}:{typ}"

record[(domain_type, target)].add(location)

Expand Down
2 changes: 1 addition & 1 deletion examples/color/color_cycle_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

axs[1, icol].set_facecolor('k')
axs[1, icol].xaxis.set_ticks(np.arange(0, 10, 2))
axs[0, icol].set_title('line widths (pts): %g, %g' % (lwx, lwy),
axs[0, icol].set_title(f'line widths (pts): {lwx:g}, {lwy:g}',
fontsize='medium')

for irow in range(2):
Expand Down
6 changes: 3 additions & 3 deletions examples/event_handling/cursor_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def on_mouse_move(self, event):
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.text.set_text(f'x={x:1.2f}, y={y:1.2f}')
self.ax.figure.canvas.draw()


Expand Down Expand Up @@ -134,7 +134,7 @@ def on_mouse_move(self, event):
x, y = event.xdata, event.ydata
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.text.set_text(f'x={x:1.2f}, y={y:1.2f}')

self.ax.figure.canvas.restore_region(self.background)
self.ax.draw_artist(self.horizontal_line)
Expand Down Expand Up @@ -206,7 +206,7 @@ def on_mouse_move(self, event):
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.text.set_text(f'x={x:1.2f}, y={y:1.2f}')
self.ax.figure.canvas.draw()


Expand Down
2 changes: 1 addition & 1 deletion examples/event_handling/pick_event_demo2.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def onpick(event):
figi, axs = plt.subplots(N, squeeze=False)
for ax, dataind in zip(axs.flat, event.ind):
ax.plot(X[dataind])
ax.text(.05, .9, 'mu=%1.3f\nsigma=%1.3f' % (xs[dataind], ys[dataind]),
ax.text(.05, .9, f'mu={xs[dataind]:1.3f}\nsigma={ys[dataind]:1.3f}',
transform=ax.transAxes, va='top')
ax.set_ylim(-0.5, 1.5)
figi.show()
Expand Down
6 changes: 2 additions & 4 deletions examples/lines_bars_and_markers/bar_label_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
ax.set_title('How fast do you want to go today?')

# Label with given captions, custom padding and annotate options
ax.bar_label(hbars, labels=['±%.2f' % e for e in error],
ax.bar_label(hbars, labels=[f'±{e:.2f}' for e in error],
padding=8, color='b', fontsize=14)
ax.set_xlim(right=16)

Expand All @@ -106,9 +106,7 @@
fig, ax = plt.subplots()
bar_container = ax.bar(animal_names, mph_speed)
ax.set(ylabel='speed in MPH', title='Running speeds', ylim=(0, 80))
ax.bar_label(
bar_container, fmt=lambda x: '{:.1f} km/h'.format(x * 1.61)
)
ax.bar_label(bar_container, fmt=lambda x: f'{x * 1.61:.1f} km/h')

# %%
#
Expand Down
1 change: 0 additions & 1 deletion examples/scales/symlog_demo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""
===========
Symlog Demo
Expand Down
2 changes: 1 addition & 1 deletion examples/shapes_and_collections/donut.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def make_circle(r):
patch = mpatches.PathPatch(path, facecolor='#885500', edgecolor='black')
ax.add_patch(patch)

ax.annotate("Outside %s,\nInside %s" % (wise(outside), wise(inside)),
ax.annotate(f"Outside {wise(outside)},\nInside {wise(inside)}",
(i * 2.5, -1.5), va="top", ha="center")

ax.set_xlim(-2, 10)
Expand Down
2 changes: 1 addition & 1 deletion examples/user_interfaces/toolmanager_sgskip.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def trigger(self, *args, **kwargs):
keys = ', '.join(sorted(self.toolmanager.get_tool_keymap(name)))
print(fmt_tool(name, tools[name].description, keys))
print('_' * 80)
fmt_active_toggle = "{0!s:12} {1!s:45}".format
fmt_active_toggle = "{!s:12} {!s:45}".format
print("Active Toggle tools")
print(fmt_active_toggle("Group", "Active"))
print('-' * 80)
Expand Down
22 changes: 11 additions & 11 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def _check_versions():

# The decorator ensures this always returns the same handler (and it is only
# attached once).
@functools.lru_cache()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these should be converted to functions.cache for clarity, in fact.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated some of them where I thought an unbounded cache was alright, but others I figured we can handle later if we want to change to unbounded rather than the default size of 128. When I was looking through the lru_cache locations it looked like we may even have some decorated instance methods with self that may be keeping instances alive, I wonder if that is causing any of the slowly growing memory leaks that have been reported...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was specifically referring to cases where the cache just serves to establish a singleton, like here.

@functools.cache
def _ensure_handler():
"""
The first time this function is called, attach a `StreamHandler` using the
Expand Down Expand Up @@ -321,7 +321,7 @@ class ExecutableNotFoundError(FileNotFoundError):
pass


@functools.lru_cache()
@functools.cache
def _get_executable_info(name):
"""
Get the version of some executable that Matplotlib optionally depends on.
Expand Down Expand Up @@ -364,7 +364,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False):
try:
output = subprocess.check_output(
args, stderr=subprocess.STDOUT,
universal_newlines=True, errors="replace")
text=True, errors="replace")
except subprocess.CalledProcessError as _cpe:
if ignore_exit_code:
output = _cpe.output
Expand Down Expand Up @@ -459,7 +459,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False):
f"version supported by Matplotlib is 3.0")
return info
else:
raise ValueError("Unknown executable: {!r}".format(name))
raise ValueError(f"Unknown executable: {name!r}")


@_api.deprecated("3.6", alternative="a vendored copy of this function")
Expand Down Expand Up @@ -757,7 +757,7 @@ def __repr__(self):
repr_split = pprint.pformat(dict(self), indent=1,
width=80 - indent).split('\n')
repr_indented = ('\n' + ' ' * indent).join(repr_split)
return '{}({})'.format(class_name, repr_indented)
return f'{class_name}({repr_indented})'

def __str__(self):
return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items())))
Expand Down Expand Up @@ -799,7 +799,7 @@ def rc_params(fail_on_error=False):
return rc_params_from_file(matplotlib_fname(), fail_on_error)


@functools.lru_cache()
@functools.cache
def _get_ssl_context():
try:
import certifi
Expand Down Expand Up @@ -1041,7 +1041,7 @@ def rc(group, **kwargs):
for g in group:
for k, v in kwargs.items():
name = aliases.get(k) or k
key = '%s.%s' % (g, name)
key = f'{g}.{name}'
try:
rcParams[key] = v
except KeyError as err:
Expand Down Expand Up @@ -1430,11 +1430,11 @@ def func(foo, label=None): ...
arg_names = arg_names[1:] # remove the first "ax" / self arg

assert {*arg_names}.issuperset(replace_names or []) or varkwargs_name, (
"Matplotlib internal error: invalid replace_names ({!r}) for {!r}"
.format(replace_names, func.__name__))
"Matplotlib internal error: invalid replace_names "
f"({replace_names!r}) for {func.__name__!r}")
assert label_namer is None or label_namer in arg_names, (
"Matplotlib internal error: invalid label_namer ({!r}) for {!r}"
.format(label_namer, func.__name__))
"Matplotlib internal error: invalid label_namer "
f"({label_namer!r}) for {func.__name__!r}")

@functools.wraps(func)
def inner(ax, *args, data=None, **kwargs):
Expand Down
18 changes: 9 additions & 9 deletions lib/matplotlib/_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ def check_shape(_shape, **kwargs):
dim_labels = iter(itertools.chain(
'MNLIJKLH',
(f"D{i}" for i in itertools.count())))
text_shape = ", ".join((str(n)
if n is not None
else next(dim_labels)
for n in target_shape))
text_shape = ", ".join(str(n)
if n is not None
else next(dim_labels)
for n in target_shape)
if len(target_shape) == 1:
text_shape += ","

Expand Down Expand Up @@ -190,8 +190,8 @@ def check_getitem(_mapping, **kwargs):
return mapping[v]
except KeyError:
raise ValueError(
"{!r} is not a valid value for {}; supported values are {}"
.format(v, k, ', '.join(map(repr, mapping)))) from None
f"{v!r} is not a valid value for {k}; supported values are "
f"{', '.join(map(repr, mapping))}") from None


def caching_module_getattr(cls):
Expand Down Expand Up @@ -219,7 +219,7 @@ def name(self): ...
if isinstance(prop, property)}
instance = cls()

@functools.lru_cache(None)
@functools.cache
def __getattr__(name):
if name in props:
return props[name].__get__(instance)
Expand Down Expand Up @@ -264,11 +264,11 @@ def method(self, *args, **kwargs):
for alias in aliases:
method = make_alias(prefix + prop)
method.__name__ = prefix + alias
method.__doc__ = "Alias for `{}`.".format(prefix + prop)
method.__doc__ = f"Alias for `{prefix + prop}`."
setattr(cls, prefix + alias, method)
if not exists:
raise ValueError(
"Neither getter nor setter exists for {!r}".format(prop))
f"Neither getter nor setter exists for {prop!r}")

def get_aliased_and_aliases(d):
return {*d, *(alias for aliases in d.values() for alias in aliases)}
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/_cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def _g34(x): return 2 * x
def _g35(x): return 2 * x - 0.5
def _g36(x): return 2 * x - 1

gfunc = {i: globals()["_g{}".format(i)] for i in range(37)}
gfunc = {i: globals()[f"_g{i}"] for i in range(37)}

_gnuplot_data = {
'red': gfunc[7],
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/_fontconfig_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ def _make_fontconfig_parser():
def comma_separated(elem):
return elem + ZeroOrMore(Suppress(",") + elem)

family = Regex(r"([^%s]|(\\[%s]))*" % (_family_punc, _family_punc))
family = Regex(fr"([^{_family_punc}]|(\\[{_family_punc}]))*")
size = Regex(r"([0-9]+\.?[0-9]*|\.[0-9]+)")
name = Regex(r"[a-z]+")
value = Regex(r"([^%s]|(\\[%s]))*" % (_value_punc, _value_punc))
value = Regex(fr"([^{_value_punc}]|(\\[{_value_punc}]))*")
# replace trailing `| name` by oneOf(_CONSTANTS) in mpl 3.9.
prop = Group((name + Suppress("=") + comma_separated(value)) | name)
return (
Expand Down
18 changes: 9 additions & 9 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ def get_unicode_index(symbol, math=False): # Publicly exported.
return tex2uni[symbol.strip("\\")]
except KeyError as err:
raise ValueError(
"'{}' is not a valid Unicode character or TeX/Type1 symbol"
.format(symbol)) from err
f"{symbol!r} is not a valid Unicode character or TeX/Type1 symbol"
) from err


VectorParse = namedtuple("VectorParse", "width height depth glyphs rects",
Expand Down Expand Up @@ -273,7 +273,7 @@ class TruetypeFonts(Fonts):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Per-instance cache.
self._get_info = functools.lru_cache(None)(self._get_info)
self._get_info = functools.cache(self._get_info)
self._fonts = {}

filename = findfont(self.default_font_prop)
Expand Down Expand Up @@ -530,7 +530,7 @@ def _get_glyph(self, fontname, font_class, sym):
except ValueError:
uniindex = ord('?')
found_symbol = False
_log.warning("No TeX to Unicode mapping for {!a}.".format(sym))
_log.warning(f"No TeX to Unicode mapping for {sym!a}.")

fontname, uniindex = self._map_virtual_font(
fontname, font_class, uniindex)
Expand Down Expand Up @@ -576,9 +576,9 @@ def _get_glyph(self, fontname, font_class, sym):
if (fontname in ('it', 'regular')
and isinstance(self, StixFonts)):
return self._get_glyph('rm', font_class, sym)
_log.warning("Font {!r} does not have a glyph for {!a} "
"[U+{:x}], substituting with a dummy "
"symbol.".format(new_fontname, sym, uniindex))
_log.warning(f"Font {new_fontname!r} does not have a glyph "
f"for {sym!a} [U+{uniindex:x}], substituting "
"with a dummy symbol.")
font = self._get_font('rm')
uniindex = 0xA4 # currency char, for lack of anything better
slanted = False
Expand Down Expand Up @@ -752,7 +752,7 @@ def _map_virtual_font(self, fontname, font_class, uniindex):

return fontname, uniindex

@functools.lru_cache()
@functools.cache
def get_sized_alternatives_for_symbol(self, fontname, sym):
fixes = {
'\\{': '{', '\\}': '}', '\\[': '[', '\\]': ']',
Expand Down Expand Up @@ -1083,7 +1083,7 @@ def __init__(self, elements):
self.glue_order = 0 # The order of infinity (0 - 3) for the glue

def __repr__(self):
return '%s<w=%.02f h=%.02f d=%.02f s=%.02f>[%s]' % (
return '{}<w={:.02f} h={:.02f} d={:.02f} s={:.02f}>[{}]'.format(
super().__repr__(),
self.width, self.height,
self.depth, self.shift_amount,
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/_type1font.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _escape(cls, match):
except KeyError:
return chr(int(group, 8))

@functools.lru_cache()
@functools.lru_cache
def value(self):
if self.raw[0] == '(':
return self._escapes_re.sub(self._escape, self.raw[1:-1])
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ def output_args(self):
args.extend(['-b', '%dk' % self.bitrate]) # %dk: bitrate in kbps.
args.extend(extra_args)
for k, v in self.metadata.items():
args.extend(['-metadata', '%s=%s' % (k, v)])
args.extend(['-metadata', f'{k}={v}'])

return args + ['-y', self.outfile]

Expand Down
Loading