Skip to content

Add unicode grapheme cluster break algorithm #74902

Closed

Description

@Vermeille
mannequin
BPO 30717
Nosy @malemburg, @loewis, @terryjreedy, @scoder, @vstinner, @benjaminp, @jwilk, @mcepl, @ezio-melotti, @stevendaprano, @bitdancer, @methane, @serhiy-storchaka, @jenstroeger, @zhangyangyu, @pganssle, @Vermeille, @bertjwregeer, @bianjp, @Manishearth
PRs
  • gh-74902: add unicode grapheme cluster break algorithm #2673
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2017-06-20.19:15:22.212>
    labels = ['interpreter-core', 'type-feature', '3.7', 'expert-unicode']
    title = 'Add unicode grapheme cluster break algorithm'
    updated_at = <Date 2021-06-29.16:33:48.790>
    user = 'https://github.com/Vermeille'

    bugs.python.org fields:

    activity = <Date 2021-06-29.16:33:48.790>
    actor = 'jwilk'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Interpreter Core', 'Unicode']
    creation = <Date 2017-06-20.19:15:22.212>
    creator = 'Guillaume Sanchez'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 30717
    keywords = []
    message_count = 27.0
    messages = ['296478', '296479', '296503', '296504', '296505', '297488', '298190', '298321', '298325', '298326', '298336', '299661', '299699', '299703', '299706', '299707', '299708', '299731', '299734', '299848', '312035', '359397', '359398', '359408', '359409', '359450', '359496']
    nosy_count = 22.0
    nosy_names = ['lemburg', 'loewis', 'terry.reedy', 'scoder', 'vstinner', 'benjamin.peterson', 'jwilk', 'mcepl', 'ezio.melotti', 'mrabarnett', 'steven.daprano', 'r.david.murray', 'methane', 'serhiy.storchaka', '_savage', 'xiang.zhang', 'p-ganssle', 'Socob', 'Guillaume Sanchez', 'Bert JW Regeer', 'bianjp', 'Manishearth']
    pr_nums = ['2673']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue30717'
    versions = ['Python 3.7']

    Linked PRs

    Activity

    1. added and removed
      stdlibStandard Library Python modules in the Lib/ directory
      on Jul 14, 2017
    2. terryjreedy commented on Jul 14, 2017

      @terryjreedy
      Member

      I think it at least plausible that we should add implementations of some of the unicode standard's algorithms. Victor and Serhiy, as two of the active core devs most involved with unicode issues, what do you think?

    3. added
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      and removed
      testsTests in the Lib/test dir
      on Jul 15, 2017
    4. removed their assignment
      on Jul 15, 2017
    5. Vermeille commented on Aug 2, 2017

      Vermeillemannequin
      MannequinAuthor

      Hi,

      Are you guys still interested? I haven't heard from you in a while

    6. changed the title [-]str.center() is not unicode aware[/-] [+]Add unicode grapheme cluster break algorithm[/+] on Aug 3, 2017
    7. serhiy-storchaka commented on Aug 3, 2017

      @serhiy-storchaka
      Member

      bpo-18406 is closed as a duplicate of this issue. There are useful links in bpo-18406. In particular see a proto-PEP of Unicode Indexing Helper Module:

      http://mail.python.org/pipermail/python-dev/2001-July/015938.html

      I agreed that providing grapheme iterator would be useful. But it would be useful to provide also word and sentence iterators.

      Should iterators provide just substrings or their positions? I think emitting a pair (pos, substring) would be more useful. It is easier to create an iterator of substrings from the iterator of pairs than opposite.

      Alternatively an iterator could emit slice objects. Or special objects similar to re match objects.

    8. Vermeille commented on Aug 3, 2017

      Vermeillemannequin
      MannequinAuthor

      Thanks for your consideration. I'm currently fixing what's been asked in the reviews.

      But it would be useful to provide also word and sentence iterators.

      I'll gladly do that as well!

      I think emitting a pair (pos, substring) would be more useful.

      That means emitting a pair like ((start, end), substr) ? Is it pythonic to return a structure like this?

      For what it's worth, I don't like it, but I definitely understand the value of it. I'd prefer having two versions. One returning indexes, the other returning substrings.

      But...

      Alternatively an iterator could emit slice objects.

      I really like that. Do we have a clear common agreement or preference on any option?

    9. Vermeille commented on Aug 3, 2017

      Vermeillemannequin
      MannequinAuthor

      I have a few criticism to do against that proto-PEP

      http://mail.python.org/pipermail/python-dev/2001-July/015938.html

      In particular, the fact that all those functions return an index prevents any state keeping.

      That's a problem because:

      next_<indextype>(u, index) -> integer

      As you've seen it, in grapheme clustering (as well as words and line breaking), we have to have an automaton to decide on the breaking point. Which means that starting at an arbitrary index is not possible.

      prev_<indextype>(u, index) -> integer

      Is it really necessary? It means implementing the same logic to go backward. In our current case, we'd need a backward grapheme cluster break automaton too.

      <indextype>_start(u, index) -> integer
      <indextype>_end(u, index) -> integer

      Not doable in O(1) for the same reason as next_<indextype>(). We need a context, and the code point itself cannot give enough information to know if it's the start/end of a given indextype.

    10. stevendaprano commented on Aug 3, 2017

      @stevendaprano
      Member

      On Thu, Aug 03, 2017 at 11:21:38AM +0000, Serhiy Storchaka wrote:

      Should iterators provide just substrings or their positions?
      [...]

      I think we're breaking new ground here and I'm not sure what the right
      API should be. Should we follow Perl 6?

      https://docs.perl6.org/type/Str

      Go has a "norm" package for dealing with normalised "characters"
      (graphemes).

      https://blog.golang.org/normalization

      http://godoc.org/golang.org/x/text/unicode/norm

      Are my comments unacceptible scope-creep? We've gone from talking about
      a grapheme cluster break algorithm to me talking about Perl6 and Go
      which have rich string APIs based on graphemes.

      I'm not even sure of the best place for this:

      • unicodedata
      • string
      • a new module?

      I don't think unicodedata is the right place -- that should be for data
      and processing of individual unicode code points, not string handling,
      and it shouldn't become a grab-bag of random unrelated functions just
      because they have something to do with Unicode.

      Can we mark this as having a Provisional API to give us time to decide on the
      best API before locking it in permanently?

      https://www.python.org/dev/peps/pep-0411/

      I'm reluctant to say this, because it's a lot more work, but maybe this
      is complicated enough that we should go through a PEP.

    11. malemburg commented on Aug 3, 2017

      @malemburg
      Member

      On 03.08.2017 15:05, Guillaume Sanchez wrote:

      Guillaume Sanchez added the comment:

      I have a few criticism to do against that proto-PEP

      http://mail.python.org/pipermail/python-dev/2001-July/015938.html

      In particular, the fact that all those functions return an index prevents any state keeping.

      If you want state keeping for iterating over multiple <indextype>
      parts of the string, you can use an iterator.

      The APIs were inspired by the standard string.find() APIs, that's
      why they work on indexes and don't return Unicode strings. As
      such, they serve a different use case than an iterator.

      With the APIs, scanning would always start at the given index
      in the string and move forward/backward to the start of the next
      <indextype>.

    12. scoder commented on Aug 4, 2017

      @scoder
      Contributor

      Wouldn't this be a typical case where we'd expect a module to evolve and gain usage on PyPI first, before adding it to the stdlib?

      Searching for "grapheme" in PyPI gives some results for me. Even if they do not cover what this ticket asks for, they might give inspiration for a suitable API design. And I'm probably missing other related packages by lack of a better search term.

      https://pypi.python.org/pypi?%3Aaction=search&term=grapheme

    13. serhiy-storchaka commented on Aug 4, 2017

      @serhiy-storchaka
      Member

      The well known library for Unicode support in C++ and Java is ICU (International Components for Unicode). There is a Python wrapper [1].

      This is a large complex library that covers many aspects of Unicode support. It's interface looks rather Javaic than Pythonic. Some parts of it already are covered by other parts of the stdlib (the str class, the codecs and locale modules).

      [1] https://pypi.python.org/pypi/PyICU/

    14. Vermeille commented on Aug 7, 2017

      Vermeillemannequin
      MannequinAuthor

      I don't think unicodedata is the right place

      I do agree with that. A new module sounds good, would it be a problem if that module would contain very few functions at first?

      Can we mark this as having a Provisional API to give us time to decide on the best API before locking it in permanently?

      I'm not sure it's my call to make, but I would gladly consider that option.

      we should go through a PEP.

      Why not. I may need a bit of guidance though.

      If you want state keeping for iterating over multiple <indextype> parts of the string, you can use an iterator.

      Sure thing. It just wasn't specified like this in the proto-PEP.

      The APIs were inspired by the standard string.find() APIs, that's why they work on indexes and don't return Unicode strings. As such, they serve a different use case than an iterator.

      I personally like having a generator returning slice objects, as suggested above. What would be some good objections to this?

      Wouldn't this be a typical case where we'd expect a module to evolve and gain usage on PyPI first, before adding it to the stdlib? [...] they might give inspiration for a suitable API design

      I'll give it a look.

      The well known library for Unicode support in C++ and Java is ICU

      Yes. I clearly don't want this PR to be interpreted as "we're needing ICU". ICU provides much much more than what I'm willing to provide. My goal here is just to "fix" how the python's standard library iterates over characters. Noting more, nothing less.

      One might think that splitlines() should be "fixed" too, and there is clearly matter to discuss here. Same for words splitting. However, I do not intend to bring normalization, which you already have, collations, locale dependant upcasing or lowercasing, etc. We might need a wheel, but we don't have to take the whole truck.

      How do we discuss all of this? Who's in charge of making decisions? How long should we debate? That's my first time contributing to Python and I'm new to all of that.

      Thanks for your time.

    15. methane commented on Feb 12, 2018

      @methane
      Member

      We missed 3.7 train.
      I'm sorry about I couldn't review it. But I have many shine features
      I want in 3.7 and I have no time to review all.
      Especially, I need to understand tr29. It was hard job to me.

      I think publishing this (and any other functions relating to unicode)
      to PyPI is better than waiting 3.8.
      It make possible to discuss API design with working code, and make it "battle tested" before adding it to standard library.

    16. Manishearth commented on Jan 6, 2020

      Manishearthmannequin
      Mannequin

      Hi,

      Unicodey person here, I'm involved in Unicode itself and also maintain an implementation of this particular spec1.

      So, firstly,

      "a⃑".center(width=5, fillchar=".")

      If you're trying to do terminal width stuff, extended grapheme clusters *will not* solve the problem for you. There is no algorithm specified in Unicode that does this, because this is font dependent. Extended grapheme clusters are better than code points for this, however, and will not ever produce *worse* results.

      It's fine to expose this, but it's worth adding caveats.

      Also, yes, please do not expose a direct indexing function. Aside from almost all Unicode algorithms being streaming algorithms and thus inefficient to index directly, needing to directly index a grapheme cluster is almost always a sign that you are making a mistake.

      Yes. I clearly don't want this PR to be interpreted as "we're needing ICU". ICU provides much much more than what I'm willing to provide. My goal here is just to "fix" how the python's standard library iterates over characters. Noting more, nothing less.

      I think it would be a mistake to make the stdlib use this for most notions of what a "character" is, as I said this notion is also inaccurate. Having an iterator library somewhere that you can use and compose is great, changing the internal workings of string operations would be a major change, and not entirely productive.

      There's only one language I can think of that uses extended grapheme clusters as its default notion of "character": Swift. Swift is largely designed for UI stuff, and it makes sense in this context. This is also baked in very deeply to the language (e.g. their Character class is a thin wrapper around String, since grapheme clusters can be arbitrarily large). You'd need a pretty major paradigm shift for python to make a similar change, and it doesn't make as much sense for python in the first place.

      Starting off with a library published to pypi makes sense to me.

    17. Manishearth commented on Jan 6, 2020

      Manishearthmannequin
      Mannequin

      Oh, also, if y'all are fine with binding to Rust (through a C ABI) I'd love to help y'all use unicode-segmentation, which is much less work that pulling in ICU. Otherwise if y'all have implementation questions I can answer them. This spec is kinda tricky to implement efficiently, but it's not super hard.

    18. stevendaprano commented on Jan 6, 2020

      @stevendaprano
      Member

      I think it would be a mistake to make the stdlib use this for most
      notions of what a "character" is, as I said this notion is also
      inaccurate. Having an iterator library somewhere that you can use and
      compose is great, changing the internal workings of string operations
      would be a major change, and not entirely productive.

      Agreed.

      I won't pretend to be able to predict what Python 5.0 will bring *wink*
      but there's too much history around the "code point = character" notion
      for the language to change now.

      If the language can expose a grapheme iterator, then people can
      experiment with grapheme-based APIs in libraries.

      (By grapheme I mean "extended grapheme cluster", but that's a mouthful.
      Sorry linguists.)

      What do you think of these as a set of grapheme primitives?

      (1) is_grapheme_break(string, i)

      Return True if a grapheme break would occur *before* string[i].

      (2) graphemes(string, start=0, end=len(string))

      Iterate over graphemes in string[start:end].

      (3) graphemes_reversed(string, start=0, end=len(string))

      Iterate over graphemes in reverse order.

      I *think* is_grapheme_break would be enough for people to implement
      their own versions of graphemes and graphemes_reversed. Here's an
      untested version:

          def graphemes(string, start, end):
              cluster = []
              for i in range(start, end):
                  c = string[i]
                  if is_grapheme_break(string, i):
                      if i != start:
                          # don't yield the empty cluster at Start Of Text
                          yield ''.join(cluster)
                      cluster = [c]
                  else:
                      cluster.append(c)
              if cluster:
                  yield ''.join(cluster)

      Regarding is_grapheme_break, if I understand the note here:

      https://www.unicode.org/reports/tr29/#Testing

      one never needs to look at more than two adjacent code points to tell
      whether or not a grapheme break will occur between them, so this ought
      to be pretty efficient. At worst, it needs to look at string[i-1] and
      string[i], if they exist.

    19. Manishearth commented on Jan 6, 2020

      Manishearthmannequin
      Mannequin

      one never needs to look at more than two adjacent code points to tell
      whether or not a grapheme break will occur between them, so this ought
      to be pretty efficient.

      That note is outdated (and has been outdated since Unicode 9). The regional indicator rules (GB12 and GB13) and the emoji rule (GB11) require arbitrary lookbehind (though thankfully not arbitrary lookahead).

      I think the ideal API surface is an iterator and nothing else. Everything else can be derived from the iterator. It's theoretically possible to expose an is_grapheme_break that's faster than just iterating -- look at the code in unicode-segmentation's _reverse_ iterator to see how -- but it's going to be tricky to get right. Building the iterator on top of is_grapheme_break is not a good idea.

    20. pganssle commented on Jan 6, 2020

      @pganssle
      Member

      Oh, also, if y'all are fine with binding to Rust (through a C ABI) I'd love to help y'all use unicode-segmentation, which is much less work that pulling in ICU. Otherwise if y'all have implementation questions I can answer them. This spec is kinda tricky to implement efficiently, but it's not super hard.

      Is the idea here that we'd take on a new dependency on the compiled unicode-segmentation binary, rather than adding Rust into our build system? Does unicode-segmentation support all platforms that CPython supports? I was under the impression that Rust requires llvm and llvm doesn't necessarily have the same support matrix as CPython (I'd love to be corrected if I'm wrong on this).

      (Note: I don't actually know what the process is for taking on new dependencies like this, just trying to point at one possible stumbling block.)

    21. Manishearth commented on Jan 7, 2020

      Manishearthmannequin
      Mannequin

      Does unicode-segmentation support all platforms that CPython supports?

      It's no-std, so it supports everything the base Rust compiler supports (which is basically everything llvm supports).

      And yeah, if there's something that doesn't match with the support matrix this isn't going to work.

      However, I suggested this more for the potential PyPI package. If you're working this into CPython you'd have to figure out how best to include Rust stuff in your build system, which seems like a giant chunk of scope creep :)

      For including in CPython I'd suggest looking through unicode-segmentation and writing a C version of it. We use a python script1 to generate the data tables, this might be something y'all can use. Swift's UAX 29 implementation is also quite interesting, however it's baked in deeply to the language so it's less useful as a starting point.

    22. transferred this issue fromon Apr 10, 2022
    23. added 2 commits that reference this issue on Dec 22, 2025
    24. serhiy-storchaka commented on Dec 22, 2025

      @serhiy-storchaka
      Member

      Since there were too many differences from #2673, I created a new PR #143076. The main differences:

      • It encodes the rules in the code instead of using a state machine. The old state machine no longer works with the recent Unicode (since 11.0.0), and I do not know how it was created at first place. If in future we will switch to a state machine, it should be generated from human readable rules, not taken from unknown source.
      • The iterators emits not strings, but objects which have start and end attributes. They can bee converted to string to get the corresponding string.
      • Exposed other properties related to the algorithm in unicodedata.
      • Added tests and documentation.
    25. serhiy-storchaka commented on Dec 22, 2025

      @serhiy-storchaka
      Member

      I also moved the state of the iterator to a separate structure. It can help when implementing unicodedata.width() which will be able to break the string on graphemes without an addition overhead.

    26. added a commit that references this issue on Jan 14, 2026
    27. added 2 commits that reference this issue on Jan 23, 2026
    28. added 3 commits that reference this issue on Feb 15, 2026
    29. added 3 commits that reference this issue on Apr 8, 2026
    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

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions