FixedLocator: copy ndarray input instead of aliasing it by pranjalm37 · Pull Request #32367 · matplotlib/matplotlib · GitHub
Skip to content

FixedLocator: copy ndarray input instead of aliasing it - #32367

Closed
pranjalm37 wants to merge 1 commit into
matplotlib:mainfrom
pranjalm37:fix/set-xticks-array-aliasing-32313
Closed

pranjalm37 wants to merge 1 commit into
matplotlib:mainfrom
pranjalm37:fix/set-xticks-array-aliasing-32313

Conversation

@pranjalm37

Copy link
Copy Markdown

Fixes #32313.

Root cause

FixedLocator.__init__ stores its tick locations via np.asarray(locs):

def __init__(self, locs, nbins=None):
    self.locs = np.asarray(locs)

np.asarray is a no-op when its input is already an ndarray — it returns the same array object, not a copy. Since Axis.set_ticks()/set_xticks() build a FixedLocator directly from whatever array the caller passed in, FixedLocator.locs ends up aliasing that array. Any later in-place mutation of it retroactively moves the axis' already-set tick locations:

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
theta = np.linspace(0, 2 * np.pi, 5, endpoint=False)
ax.set_xticks(theta)
theta += 2 * np.pi  # unrelated in-place mutation elsewhere in the program
# ax's ticks silently moved too

A plain list input doesn't show this, since converting a list to an array always produces a fresh one — which is presumably why this went unnoticed for a while.

Fix

Use np.array(locs) instead, which copies by default, so FixedLocator owns its storage regardless of whether the input was a list or an existing ndarray.

Testing

  • Added TestFixedLocator.test_locs_is_a_copy_of_ndarray_input, which constructs a FixedLocator from an ndarray, mutates the array in place afterward, and asserts .locs is unaffected. Verified it fails on unpatched ticker.py (fixed.locs is not locs → AssertionError) and passes with the fix.
  • Manually reproduced the original report's polar-plot example end-to-end (prebuilt matplotlib wheel with just ticker.py swapped in, since I couldn't build the C extensions from source in my sandbox — no network access for the bundled freetype/etc. fetch): confirmed ax.get_xticks() changes after mutating the input array on unpatched code, and stays stable with the fix. Also confirmed a plain-list set_xticks([1, 2, 3]) call is unaffected.
  • ruff check on both changed files — clean (matplotlib's pyproject.toml only configures ruff check, not ruff format, so I didn't run the formatter).

AI disclosure

I used Claude Code to investigate and draft this fix; I reviewed the diagnosis (traced set_ticks → FixedLocator.__init__ → np.asarray), verified the reproduction and fix behavior myself as described above, and reviewed every changed line before opening this PR.


🤖 Generated with Claude Code

FixedLocator.__init__ stored locs via np.asarray(locs), which is a
no-op for an already-ndarray input -- it returns the same object
rather than a copy. Since set_ticks()/set_xticks() build their
FixedLocator directly from whatever array the caller passed, any
later in-place mutation of that array (e.g. `theta[i] += ...`) moved
the axis' already-set tick locations too, silently changing the plot.
A plain list input didn't have this problem, since converting a list
always produces a fresh array.

Use np.array(locs) instead, which copies by default, so FixedLocator
owns its own storage regardless of the input type.

Fixes matplotlib#32313.
@github-actions

Copy link
Copy Markdown

@rcomer

rcomer commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Hi @pranjalm37 and thank you for your interest in improving Matplotlib. From your PR description, it does not look like you read the comments on the issue. Please read those comments and say why you think this fix is still necessary in the light of them.

@pranjalm37

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Modifying NumPy array after passing to set_xticks() unexpectedly alters plot ticks

2 participants