Skip to content

DEP: deprecate implicit bytes to string conversion in np.strings - #32757

Open
ngoldbaum wants to merge 7 commits into
numpy:mainfrom
ngoldbaum:deprecate-implicit-conv
Open

ngoldbaum wants to merge 7 commits into
numpy:mainfrom
ngoldbaum:deprecate-implicit-conv

Conversation

@ngoldbaum

@ngoldbaum ngoldbaum commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

PR summary

Over in https://github.com/numpy/numpy/pull/32749/changes#r4072990536 @jorenham and I identified an inconsistency with how python handles mixed string and bytes operands in string and bytes methods.

First, this changes a number of default arguments in np.strings from empty string to None. This allows us to properly handle bytes operands for these functions and also avoid emitting warnings when the argument is not explicitly passed. To avoid breaking possible downstream __array_function__ implementations, I only pass these arguments if a user sets them, so downstream can continue assuming the argument is always a string.

Second, it deprecates calls like np.strings.replace(b"abc", "a", "x") that silently mix text and bytes. This is deprecated for replace, partition, and text-padding functions. My AI model found concrete downstream sites in where this pattern happens:

  • AtomWorks: casts atom names to S4, then calls np.char.ljust(..., fillchar=" ").
  • NVIDIA Proteina-Complexa: the same padding pattern.
  • ugali: calls np.char.replace(objects['NAME'], '_', ' ') on an S24 field.

It also adds machinery in _array_converter to facilitate these checks.

Finally, updates the type stubs to match these changes.

AI Disclosure

I iterated on this using an AI model.

Comment thread numpy/_core/strings.pyi
Comment thread doc/release/upcoming_changes/32757.deprecation.rst Outdated
Comment thread doc/release/upcoming_changes/32757.deprecation.rst Outdated
Comment thread numpy/_core/strings.py Outdated
Comment thread numpy/_core/tests/test_deprecations.py Outdated
Comment thread doc/release/upcoming_changes/32757.deprecation.rst Outdated

@seberg seberg left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two comments. I'd love to fix this one level lower, but since I am not sure how viable it is and strings need some annoying extra handling anyway (e.g. to deal with the default), I am not opposed to this.

It does seem like there is absolutely no reason not to use None as default, though?

Comment thread numpy/_core/strings.py
"The fill character must be exactly one character long")
fillchar = _get_fillchar(a, fillchar)

if np.result_type(a, fillchar).char == "T":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it doesn't matter, but as a note. There is an underlying issue here that:

np.result_type() happily promotes stringes/bytes and everything else to strings. I would love to fix this, but IIRC there was some fun fallout (that may be fixable, IIRC a main thing was that some code relied on it working for later things to not fail).

That means that np.concatenate(([b"1", "2"])) just as happily works for example.

(I think that can_cast("U1", "S1", casting="safe") is true is probably even fine.)

Bringing this up not to stop it, but to say that there is more to this and I suppose I semi-care about this if we don't attack the rest. It might even be interesting to start with result_type(..., strict_strings=True) even if private.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It might even be interesting to start with result_type(..., strict_strings=True) even if private.

I went ahead and did that because I think it's a cleaner fix. I'm also about to file a followup issue citing your comment about this.

Comment thread numpy/_core/strings.py Outdated
@set_module("numpy.strings")
@array_function_dispatch(_just_dispatcher)
def center(a, width, fillchar=' '):
def center(a, width, fillchar=np._NoValue):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I really don't see why you use np._NoValue, when None should do, I think? The only reason I can see would be that None currently actually places the string "None"

@jorenham jorenham Sep 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm guessing it's because StringDType and upcoming ByteStringDType could use None as na_object?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

None works! Even if it comes from missing data, it still means "use a space". That's not inconsistent. Also it's a super corner case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok nice, None is a bit cleaner in the stubs as well because then we can fill in the = ...

@ngoldbaum
ngoldbaum force-pushed the deprecate-implicit-conv branch from 41a0d05 to 52514f5 Compare September 23, 2026 17:28
@ngoldbaum

Copy link
Copy Markdown
Member Author

I ended up applying part of the deeper structural solution @seberg was thinking about. It does make the diff bigger though, let me know what you think. I'll file a followup issue about the array coercion issues that Sebastian pointed out.

ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 23, 2026
ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 23, 2026
ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 24, 2026
ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 24, 2026
ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 24, 2026
ngoldbaum added a commit to ngoldbaum/numpy that referenced this pull request Sep 24, 2026

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants