Skip to content

The function unicodedata.normalize() should always return an instance of the built-in str type. #129569

Closed
webforfutureinc/cpython
#31

Description

@Hizuru3

Bug report

Bug description:

The current implementation of unicodedata.normalize() returns a new reference to the input string when the data is already normalized. It is fine for instances of the built-in str type. However, if the function receives an instance of a subclass of str, the return type becomes inconsistent.

import unicodedata

class MyStr(str):
	pass

s1 = unicodedata.normalize('NFKC', MyStr('Å')) # U+00C5 (already normalized)
s2 = unicodedata.normalize('NFKC', MyStr('Å')) # U+0041 U+030A (not normalized)

print(type(s1), type(s2))		# <class '__main__.MyStr'> <class 'str'>

In addition, passing instances of user-defined str subclasses can lead to unexpected sharing of modifiable attributes:

import unicodedata

class MyStr(str):
	pass


original = MyStr('ascii string')
original.is_original = True

verified = unicodedata.normalize('NFKC', original)
verified.is_original = False

print(original.is_original)		# False

The solution would be to use the PyUnicode_FromObject() API for early returns in the normalize() function implementation instead of Py_NewRef() to make sure that the function always returns an instance of the built-in str type.

CPython versions tested on:

3.11, 3.13

Operating systems tested on:

Windows

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Feb 2, 2025
  2. picnixz commented on Feb 2, 2025

    @picnixz
    Member

    Is it really an issue that we return the object as is? Can't we document that behaviour instead? Some existing code may rely on this feature so I think we should have a proper depreciation period where we would warn in the future that non-str objects will not be accepted and that their underlying str() representation will be used.

    We also need to document that change. I'm not against making a true string for the normalized value but this needs to be communicated properly to the end user.

  3. added
    type-featureA feature request or enhancement
    and removed
    type-bugAn unexpected behavior, bug, or error
    on Feb 2, 2025
  4. serhiy-storchaka commented on Feb 2, 2025

    @serhiy-storchaka
    Member

    I consider this a bug. An optimization should not affect the type of the result, and returning the original object is merely an optimization.

  5. picnixz commented on Feb 2, 2025

    @picnixz
    Member

    I guess it also makes sense in that sense. Ok for categorizing it as a bug then.

  6. picnixz commented on Feb 2, 2025

    @picnixz
    Member

    Note that @corona10 removed the backports labels on the PR so maybe we could discuss whether to back port this here as well?

  7. added
    type-bugAn unexpected behavior, bug, or error
    and removed
    type-featureA feature request or enhancement
    on Feb 2, 2025
  8. corona10 commented on Feb 2, 2025

    @corona10
    Member

    If @serhiy-storchaka considers this a bug, then we can backport it. I was confused between this as behavior changes or a bug :)

  9. added a commit that references this issue on Feb 21, 2025
  10. added 2 commits that reference this issue on Feb 21, 2025
  11. malemburg commented on Feb 21, 2025

    @malemburg
    Member

    Since this is a breaking change, shouldn't there be a deprecation period applied ? Also note that this behavior has been in existence for 20+ years.

    Also see https://discuss.python.org/t/the-function-unicodedata-normalize-should-always-return-an-instance-of-the-built-in-str-type/79090/10 for the discussion.

  12. vstinner commented on Feb 21, 2025

    @vstinner
    Member

    Oh, I didn't notice that there was a discussion on discuss.python.org about this change.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions