GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-129117: Expose `_PyUnicode_IsXidContinue/Start` in `unicodedata` by StanFromIreland · Pull Request #140269 · python/cpython

Conversation

@StanFromIreland

@StanFromIreland StanFromIreland commented Oct 17, 2025 •
edited by github-actions Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member
  • Issue: unicodedata module needs way of accurately determining XID_START and XID_CONTINUE properties. #129117

📚 Documentation preview 📚: https://cpython-previews--140269.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@StanFromIreland
Commit
c0511cc
@StanFromIreland
StanFromIreland requested a review from malemburg October 17, 2025 19:46
@StanFromIreland
StanFromIreland requested a review from AA-Turner as a code owner October 17, 2025 19:46
@bedevere-app bedevere-app Bot added the awaiting review label Oct 17, 2025
@bedevere-app bedevere-app Bot mentioned this pull request Oct 17, 2025
unicodedata module needs way of accurately determining XID_START and XID_CONTINUE properties. #129117
Closed
@StanFromIreland
StanFromIreland requested a review from ezio-melotti October 17, 2025 19:59
@StanFromIreland
Fix linking on windows and refactor test
83bb3a9
vstinner
vstinner reviewed Oct 18, 2025
View reviewed changes
Comment thread Include/cpython/unicodeobject.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

@StanFromIreland
Move to pycore_unicodedata.h
571c622
@StanFromIreland
StanFromIreland requested review from a team, emmatyping and erlend-aasland as code owners October 18, 2025 22:03
@StanFromIreland
lint
9043865
@StanFromIreland
StanFromIreland requested a review from vstinner October 18, 2025 22:04
@vstinner

vstinner commented Oct 28, 2025

Copy link
Copy Markdown
Member

@StanFromIreland StanFromIreland requested a review from vstinner

I don't know these Unicode properties. The PR documentation doesn't help me:

Return True if the character has the XID_Start property

What does it mean XID_Start?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@StanFromIreland

StanFromIreland commented Oct 28, 2025

Copy link
Copy Markdown
Member Author

Ah no worries then. You can find their documentation in this report, I can add a link to it in the docs.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Oct 28, 2025

Copy link
Copy Markdown
Member

In short, these functions check if a character is an identifier start or an identifier character according to Unicode TR31?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@StanFromIreland

StanFromIreland commented Oct 28, 2025

Copy link
Copy Markdown
Member Author

Yes.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes
Comment thread Include/internal/pycore_unicodedata.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Doc/library/unicodedata.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Doc/library/unicodedata.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/unicodedata.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Objects/unicodectype.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

StanFromIreland added 2 commits October 29, 2025 13:36
@StanFromIreland
Part of review
cf197af
@StanFromIreland
Review
b24b994
@StanFromIreland
StanFromIreland requested a review from vstinner October 29, 2025 13:47
StanFromIreland added 2 commits October 29, 2025 15:34
@StanFromIreland
Revert "Review"
b9ae70d
This reverts commit b24b994.
@StanFromIreland
Third times the charm
dc0752f
vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes
Comment thread Include/internal/pycore_unicodectype.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Include/internal/pycore_unicodeobject.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/unicodedata.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@StanFromIreland
Review
c958c2a
malemburg
malemburg approved these changes Oct 29, 2025
View reviewed changes

@malemburg malemburg left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

LGTM now

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels Oct 29, 2025
malemburg
malemburg requested changes Oct 29, 2025
View reviewed changes
Comment thread Include/internal/pycore_unicodeobject.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

@StanFromIreland

StanFromIreland commented Oct 29, 2025

Copy link
Copy Markdown
Member Author

Thanks for the reviews!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes
Comment thread Doc/library/unicodedata.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The change is correct, but I'm not convinced that we have to expose this feature in Python. It seems to be an Unicode feature which rarely used.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@StanFromIreland
Split sentance in docs
14c9536
@malemburg

malemburg commented Oct 29, 2025

Copy link
Copy Markdown
Member

Have a look at https://peps.python.org/pep-3131/ for why these are important to have.

vstinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

About function names, the Unicode annex has also ID_Start and ID_Continue. The XID is a variant. Maybe we should keep x in the function names?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@StanFromIreland

StanFromIreland commented Oct 29, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

About function names, the Unicode annex has also ID_Start and ID_Continue.

Note that they explicitly recommend the "X" variants.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@malemburg

malemburg commented Oct 29, 2025

Copy link
Copy Markdown
Member

Maybe we should keep x in the function names?

You have a point there. Let's keep the "x" in "xid" for the functions to not cause confusion.

StanFromIreland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

StanFromIreland added 2 commits October 29, 2025 19:19
@StanFromIreland
"X"
6fe1ab8
@StanFromIreland
More "X"
5ccc2cd
vstinner
vstinner approved these changes Oct 29, 2025
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

LGTM

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
vstinner
vstinner reviewed Oct 29, 2025
View reviewed changes
Comment thread Doc/whatsnew/3.15.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

StanFromIreland and others added 2 commits October 29, 2025 20:11
@StanFromIreland
Expand What's New and blurb
4900fb7
@StanFromIreland
Merge branch 'main' into startcontinueid
d6133f2
vstinner
vstinner reviewed Oct 30, 2025
View reviewed changes
Comment thread Doc/library/unicodedata.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
Update Doc/library/unicodedata.rst
50300f4
@vstinner
vstinner enabled auto-merge (squash) October 30, 2025 09:53
@vstinner
vstinner merged commit dbe3950 into python:main Oct 30, 2025
4 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Oct 30, 2025
@StanFromIreland
StanFromIreland deleted the startcontinueid branch October 30, 2025 10:21
@StanFromIreland

StanFromIreland commented Oct 30, 2025

Copy link
Copy Markdown
Member Author

Thanks for merging!

vstinner reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou

encukou commented Nov 4, 2025

Copy link
Copy Markdown
Member

Have a look at https://peps.python.org/pep-3131/ for why these are important to have.

It's still not clear to me why isidentifier() is not enough here.

Note that neither PEP-3131 nor current Python use the Unicode definition of XID_Start to determine identifiers -- they additionally allow the underscore:

>>> unicodedata.isxidstart('_')
False
>>> '_'.isidentifier()
True

Also, there's an easier way to explain name parsing, which involves only id_start & id_continue, and not the xid variants (whose definitions are more complicated): #140464 (review)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@malemburg

malemburg commented Nov 4, 2025

Copy link
Copy Markdown
Member

Python uses this internally as part of figuring out what a valid identified is, but XID_Start/End are also important to be able to parse other languages which use these are basis for their identifier definitions.

Note that you need to use the XID variants if you are working with NFKC normalized text. See https://www.unicode.org/reports/tr31/#NFKC_Modifications

From unicodeobject.c:

_PyUnicode_ScanIdentifier(PyObject *self)
{
    Py_ssize_t i;
    Py_ssize_t len = PyUnicode_GET_LENGTH(self);
    if (len == 0) {
        /* an empty string is not a valid identifier */
        return 0;
    }

    int kind = PyUnicode_KIND(self);
    const void *data = PyUnicode_DATA(self);
    Py_UCS4 ch = PyUnicode_READ(kind, data, 0);
    /* PEP 3131 says that the first character must be in
       XID_Start and subsequent characters in XID_Continue,
       and for the ASCII range, the 2.x rules apply (i.e
       start with letters and underscore, continue with
       letters, digits, underscore). However, given the current
       definition of XID_Start and XID_Continue, it is sufficient
       to check just for these, except that _ must be allowed
       as starting an identifier.  */
    if (!_PyUnicode_IsXidStart(ch) && ch != 0x5F /* LOW LINE */) {
        return 0;
    }

    for (i = 1; i < len; i++) {
        ch = PyUnicode_READ(kind, data, i);
        if (!_PyUnicode_IsXidContinue(ch)) {
            return i;
        }
    }
    return i;
}

The underscore is a special exception added for Python.

StanFromIreland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou

encukou commented Nov 5, 2025

Copy link
Copy Markdown
Member

XID_Start/End are also important to be able to parse other languages which use these are basis for their identifier definitions.

OK, that's a valid reason. Thanks!
I'll add a note about _ to clear up confusion.

Note that you need to use the XID variants if you are working with NFKC normalized text.

Couldn't you also first normalize and then use the ID variants on the result? (Asking to confirm my understanding, as I'll probably be explaining this to others.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@malemburg

malemburg commented Nov 5, 2025

Copy link
Copy Markdown
Member

Note that you need to use the XID variants if you are working with NFKC normalized text.

Couldn't you also first normalize and then use the ID variants on the result? (Asking to confirm my understanding, as I'll probably be explaining this to others.)

No, since the normalization creates a few special cases which the ID variants won't handle. From the tech report: "Where programming languages are using NFKC to fold differences between characters, they need the following modifications of the identifier syntax from the Unicode Standard to deal with the idiosyncrasies of a small number of characters. These modifications are reflected in the XID_Start and XID_Continue properties."

See https://www.unicode.org/reports/tr31/#NFKC_Modifications for details.

Since Python is doing exactly that (normalizing to NFKC before parsing), it needs to use the XID variants.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou

encukou commented Nov 5, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

AFAIK, these modifications are exactly what's covered by normalizing and checking the result.
For the first example there, a THAI CHARACTER SARA AM is a Lo, which puts it in ID_Start:

>>> unicodedata.category('\N{THAI CHARACTER SARA AM}')
'Lo'

But normalizing turns it into 2 characters, Mn and Lo:

>>> [(unicodedata.name(c), unicodedata.category(c)) for c in unicodedata.normalize('NFKC', '\N{THAI CHARACTER SARA AM}')]
[('THAI CHARACTER NIKHAHIT', 'Mn'), ('THAI CHARACTER SARA AA', 'Lo')]

The NIKHAHIT (Mn) is in ID_Continue but not ID_Start, which means the SARA AM can't start an identifier despite being a letter:

>>> '\N{THAI CHARACTER SARA AM}'.isidentifier()
False

That is: using the XID properties before normalization will get you the same result as using the ID ones after normalization. IOW, you need to use the XID variants if you are not working with NFKC normalized text.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@malemburg

malemburg commented Nov 5, 2025

Copy link
Copy Markdown
Member

That is: using the XID properties before normalization will get you the same result as using the ID ones after normalization. IOW, you need to use the XID variants if you are not working with NFKC normalized text.

Rereading the section in the TR, you could be right in a way 🙂

It discusses closure under normalization and this essentially means that the isIdentifier() property should give the same results regardless of whether it is applied to normalized text or raw text.

Using the XID variants to implement isIdentifier() will get you this property.

Python uses the XID variants on NFKC normalized text (since it has to normalize anyway) and so the results with respect to being identifiers are the same.

Applications parsing other languages may choose to not normalize first, so for them the XID variants are beneficial as well.

In other places in the TR, it recommends always using the XID variants: "They are recommended for most purposes, especially for security, over the original ID_Start and ID_Continue properties." (see https://www.unicode.org/reports/tr31/#Default_Identifier_Syntax and https://www.unicode.org/reports/tr31/#Migration).

In fact, most of the TR was updated to use the XID variants instead of the ID ones, with the ID variantes only left in for backwards compatibility with Unicode versions prior to version 9.

So all in all, you're right in that the purpose of using XID is more generic and can be applied before or after normalization, giving the same results. In addition, it's also safer, since your text may in some cases be half normalized and half raw and XID will still do a proper job, whereas ID may fail in some edge cases.

StanFromIreland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou

encukou commented Nov 5, 2025

Copy link
Copy Markdown
Member

Ah! It all makes sense now. Thank you!

malemburg and StanFromIreland reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou encukou mentioned this pull request Nov 5, 2025
gh-135676: Simplify docs on lexing names #140464
Merged
StanFromIreland added a commit to StanFromIreland/cpython that referenced this pull request Dec 6, 2025
@StanFromIreland @vstinner
pythongh-129117: Add unicodedata.isxidstart() function (python#140269)
2b0a702
Expose `_PyUnicode_IsXidContinue/Start` in `unicodedata`:
add isxidstart() and isxidcontinue() functions.

Co-authored-by: Victor Stinner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@vstinner vstinner vstinner approved these changes
@malemburg malemburg malemburg approved these changes
@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner is a code owner
@ezio-melotti ezio-melotti Awaiting requested review from ezio-melotti
@erlend-aasland erlend-aasland Awaiting requested review from erlend-aasland erlend-aasland is a code owner
@emmatyping emmatyping Awaiting requested review from emmatyping emmatyping is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@StanFromIreland @vstinner @malemburg @encukou
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.