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-110289: C API: Add PyUnicode_EqualToUTF8() function by serhiy-storchaka · Pull Request #110297 · python/cpython

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Oct 3, 2023 •
edited by github-actions Bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member
  • Issue: C API: Add PyUnicode_EqualToUTF8() function #110289

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

Sorry, something went wrong.

Uh oh!

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

All reactions
@serhiy-storchaka
pythongh-110289: C API: Add PyUnicode_EqualToString() function
d39945e
@serhiy-storchaka
serhiy-storchaka requested a review from vstinner October 3, 2023 16:03
@bedevere-app bedevere-app Bot mentioned this pull request Oct 3, 2023
C API: Add PyUnicode_EqualToUTF8() function #110289
Closed
vstinner
vstinner reviewed Oct 3, 2023
View reviewed changes

ghost 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.

PyUnicode_EqualToString() inline UTF-8 encoder is hard for review for me right now, I would feel more comfortable with tests, especially on corner cases:

  • string not encoded to UTF-8
  • Evil surrogate characters

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Objects/unicodeobject.c Outdated
assert(str);
if (PyUnicode_IS_ASCII(unicode)) {
size_t len = (size_t)PyUnicode_GET_LENGTH(unicode);
return strlen(str) == len &&

ghost Oct 3, 2023

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.

I would prefer to test the length first, to make the code more readable.

Like:

if (strlen(str) == len) {
    return 1;
}
return memcmp(...);

Same below.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 3, 2023

Copy link
Copy Markdown
Member Author

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.

It is the same in _PyUnicode_EqualToASCIIString().

How

if (!a) {
    return 0;
}
return b;

is more readable than simple return a && b;? It is what the && operator for.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

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.

is more readable than simple return a && b;?

For me, it's easier to reason about a single test per line when I review code.

Keep a && b if you prefer.

Sorry, something went wrong.

Uh oh!

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

erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

ghost Oct 4, 2023

Copy link
Copy Markdown
Contributor

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 readability problem as I see it, is that your && use has side effects; it is not a pure logic expression.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

For me, it's easier to reason about a single test per line when I review code.

Fortunately, every condition here is already on a separate line.

The readability problem as I see it, is that your && use has side effects; it is not a pure logic expression.

It is how && works in C. There is a lot of code like arg != NULL and PyDict_Check(arg) && PyDict_GET_SIZE(arg) > count. I do not think rewriting it in three ifs with gotos can improve readability.

Sorry, something went wrong.

Uh oh!

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

All reactions
@vstinner

ghost commented Oct 3, 2023

Copy link
Copy Markdown
Member

Suggestion for a different function name to avoid any confusion... and make it shorter: PyUnicode_EqualToUTF8().

All reactions

Sorry, something went wrong.

Uh oh!

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

Serhiy Storchaka added 2 commits October 3, 2023 21:20
Serhiy Storchaka
Add tests and address review comments.
4793161
Serhiy Storchaka
Merge branch 'main' into capi-PyUnicode_EqualToString
8b24911
@serhiy-storchaka

ghost commented Oct 3, 2023

Copy link
Copy Markdown
Member Author

I considered two variants: PyUnicode_EqualToUTF8String() and PyUnicode_EqualToString().

All reactions

Sorry, something went wrong.

Uh oh!

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

@serhiy-storchaka serhiy-storchaka changed the title gh-110289: C API: Add PyUnicode_EqualToString() function gh-110289: C API: Add PyUnicode_EqualToUTF8() function Oct 3, 2023
vstinner
vstinner reviewed Oct 4, 2023
View reviewed changes
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Outdated
Comment on lines 1401 to 1402
Compare a Unicode object with a UTF-8 encoded C string and return true
if they are equal and false otherwise.

ghost Oct 4, 2023

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.

Suggested change
Compare a Unicode object with a UTF-8 encoded C string and return true
if they are equal and false otherwise.
Compare a Unicode object with a UTF-8 encoded C string and return non-zero
if they are equal or 0 otherwise.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

It looks to me, that "return true" is more often used than "return non-zero". In this case it is more accurate, because it always returns 1, not other non-zero value. Perhaps other functions which return non-zero was a macro that returned not 1 (something like (arg->flags & FLAG))?

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Doc/whatsnew/3.13.rst Outdated
Comment on lines 1005 to 1006
a :c:expr:`const char*` UTF-8 encoded bytes string and return true if they
are equal or false otherwise.

ghost Oct 4, 2023

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.

Suggested change
a :c:expr:`const char*` UTF-8 encoded bytes string and return true if they
are equal or false otherwise.
a :c:expr:`const char*` UTF-8 encoded bytes string and return non-zero if they
are equal or 0 otherwise.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

vstinner
vstinner reviewed Oct 4, 2023
View reviewed changes
Comment thread Objects/unicodeobject.c Outdated
}
else if (ch < 0x800) {
if ((0xc0 | (ch >> 6)) != (unsigned char)*str++ ||
(0x80 | (ch & 0x3f)) != (unsigned char)*str++)

ghost Oct 4, 2023

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.

unsigned char byte1 = (0xc0 | (ch >> 6));
unsigned char byte2 = (0x80 | (ch & 0x3f));
if (str[0] != byte1 || str[1] != byte2) return 0;

And declare a str variable as unsigned char* once to avoid casting str at each byte comparison.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

If the first comparison fails, you do not need to calculate the second byte. The code looks more compact and uniform in the way it is written right now. All expressions I copied from the UTF-8 encoder which I wrote 11 years ago, so no need to recheck them. Casting to unsigned char is not a large burden, but if you prefer, I can introduce a new unsigned char* variable.

Sorry, something went wrong.

Uh oh!

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

All reactions
Serhiy Storchaka and others added 2 commits October 4, 2023 10:53
Serhiy Storchaka Victor Stinner
Apply suggestions from code review
c55f9ac
Co-authored-by: Victor Stinner <[email protected]>
Serhiy Storchaka
Address some of review comments and test the UTF-8 cache.
bdf2f1e
@serhiy-storchaka
serhiy-storchaka marked this pull request as ready for review October 4, 2023 08:35
@serhiy-storchaka
serhiy-storchaka requested review from a team and encukou as code owners October 4, 2023 08:35
@bedevere-app bedevere-app Bot added the awaiting review label Oct 4, 2023
vstinner
vstinner reviewed Oct 4, 2023
View reviewed changes
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Outdated

.. c:function:: int PyUnicode_EqualToUTF8(PyObject *unicode, const char *string)

Compare a Unicode object with a UTF-8 encoded C string and return true (``1``)

ghost Oct 4, 2023

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.

Suggested change
Compare a Unicode object with a UTF-8 encoded C string and return true (``1``)
Compare a Unicode object with a UTF-8 encoded or ASCII encoding C string and return true (``1``)

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

Maybe "ASCII encoded"?

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Objects/unicodeobject.c Outdated
assert(str);
if (PyUnicode_IS_ASCII(unicode)) {
size_t len = (size_t)PyUnicode_GET_LENGTH(unicode);
return strlen(str) == len &&

ghost Oct 4, 2023

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.

is more readable than simple return a && b;?

For me, it's easier to reason about a single test per line when I review code.

Keep a && b if you prefer.

Sorry, something went wrong.

Uh oh!

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

erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

erlend-aasland
erlend-aasland reviewed Oct 4, 2023
View reviewed changes
Comment thread Modules/_testcapi/unicode.c Resolved

Uh oh!

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

Comment thread Objects/unicodeobject.c Outdated
assert(str);
if (PyUnicode_IS_ASCII(unicode)) {
size_t len = (size_t)PyUnicode_GET_LENGTH(unicode);
return strlen(str) == len &&

ghost Oct 4, 2023

Copy link
Copy Markdown
Contributor

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 readability problem as I see it, is that your && use has side effects; it is not a pure logic expression.

Sorry, something went wrong.

Uh oh!

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

All reactions
Serhiy Storchaka
Address review comments.
7223c14
@serhiy-storchaka

ghost commented Oct 4, 2023

Copy link
Copy Markdown
Member Author

I tried to rewrite the code in more vertically sparse way:

Details
int
PyUnicode_EqualToUTF8(PyObject *unicode, const char *str)
{
    assert(_PyUnicode_CHECK(unicode));
    assert(str);

    if (PyUnicode_IS_ASCII(unicode)) {
        size_t len = (size_t)PyUnicode_GET_LENGTH(unicode);
        if (strlen(str) != len) {
            return 0;
        }
        if (memcmp(PyUnicode_1BYTE_DATA(unicode), str, len) != 0) {
            return 0;
        }
        return 1;
    }
    if (PyUnicode_UTF8(unicode) != NULL) {
        size_t len = (size_t)PyUnicode_UTF8_LENGTH(unicode);
        if (strlen(str) != len) {
            return 0;
        }
        if (memcmp(PyUnicode_UTF8(unicode), str, len) != 0) {
            return 0;
        }
        return 1;
    }

    const unsigned char *s = (const unsigned char *)str;
    Py_ssize_t len = PyUnicode_GET_LENGTH(unicode);
    int kind = PyUnicode_KIND(unicode);
    const void *data = PyUnicode_DATA(unicode);
    /* Compare Unicode string and UTF-8 string */
    for (Py_ssize_t i = 0; i < len; i++) {
        Py_UCS4 ch = PyUnicode_READ(kind, data, i);
        if (ch == 0) {
            return 0;
        }
        else if (ch < 0x80) {
            if (s[0] != ch) {
                return 0;
            }
            s += 1;
        }
        else if (ch < 0x800) {
            if (s[0] != (0xc0 | (ch >> 6))) {
                return 0;
            }
            if (s[1] != (0x80 | (ch & 0x3f))) {
                return 0;
            }
            s += 2;
        }
        else if (ch < 0x10000) {
            if (Py_UNICODE_IS_SURROGATE(ch)) {
                return 0;
            }
            if (s[0] != (0xe0 | (ch >> 12))) {
                return 0;
            }
            if (s[1] != (0x80 | ((ch >> 6) & 0x3f))) {
                return 0;
            }
            if (s[2] != (0x80 | (ch & 0x3f))) {
                return 0;
            }
            s += 3;
        }
        else {
            assert(ch <= MAX_UNICODE);
            if (s[0] != (0xf0 | (ch >> 18))) {
                return 0;
            }
            if (s[1] != (0x80 | ((ch >> 12) & 0x3f))) {
                return 0;
            }
            if (s[2] != (0x80 | ((ch >> 6) & 0x3f))) {
                return 0;
            }
            if (s[3] != (0x80 | (ch & 0x3f))) {
                return 0;
            }
            s += 4;
        }
    }
    return *s == 0;
}
and it causes dizziness and eye pain in me. It is physically difficult for me to read it.
All reactions

Sorry, something went wrong.

Uh oh!

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

vstinner
vstinner reviewed Oct 4, 2023
View reviewed changes
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Outdated
# CRASHES equaltoutf8(b'abc', b'abc')
# CRASHES equaltoutf8([], b'abc')
# CRASHES equaltoutf8(NULL, b'abc')
# CRASHES equaltoutf8('abc') # NULL

ghost Oct 4, 2023

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.

Suggested change
# CRASHES equaltoutf8('abc') # NULL
# CRASHES equaltoutf8('abc', NULL)

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

No, it does not work so.

NULL is defined as None, and equaltoutf8('abc', None) is a TypeError.

If equaltoutf8() is called with only one argument, it sets the second argument for PyUnicode_EqualToUTF8() to NULL, so we can test it and ensure that it indeed crashes. It is a common approach used in other tests in this file for const char * argument. Some functions do not crash, but raise exception or return successfully for NULL, but this function simply crashes in debug build.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

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.

Oh ok, I thought that they were just pseudo-code as comments. Sure, you can leave # NULL if you prefer.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 4, 2023

Copy link
Copy Markdown
Member Author

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.

Hmm, I copied this pattern from the test for PyUnicode_CompareWithASCIIString() which was one of the first written tests. In newer tests I use "z#" which allows to pass None for NULL. Or perhaps I changed this everywhere except the test for PyUnicode_CompareWithASCIIString(). So perhaps I can change this too.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Modules/_testcapi/unicode.c Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Doc/whatsnew/3.13.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

@vstinner vstinner mentioned this pull request Oct 4, 2023
Add PyUnicode_EqualToUTF8() function python/pythoncapi-compat#78
Merged
Serhiy Storchaka Victor Stinner
Apply suggestions from code review
b271327
Co-authored-by: Victor Stinner <[email protected]>
@vstinner

ghost commented Oct 4, 2023

Copy link
Copy Markdown
Member

I prepared a PR to add this function to Python 2.7-3.12 in the pythoncapi-compat project: python/pythoncapi-compat#78

I chose to write a simple implementation:

        utf8 = PyUnicode_AsUTF8AndSize(unicode, &len);
        if (utf8 == NULL) {
            // Memory allocation failure. The API cannot report error,
            // so clear the exception and return 0.
            PyErr_Clear();
            return 0;
        }

It's tempting to ask you to modify the API to return -1 on error, but on the other side I hate APIs with simple tasks like "compare two strings" which can fail :-( Most people simply... don't check for errors.

So well, I like the propose API, function which cannot fail.

All reactions

Sorry, something went wrong.

Uh oh!

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

Serhiy Storchaka
Remove trailing spaces.
6f26ad6
@serhiy-storchaka

ghost commented Oct 4, 2023

Copy link
Copy Markdown
Member Author

Oh, other features of this implementation:

  • It can be called when an error is set and preserves it.
  • It does not use heap, so it can be used when MemoryError has been raised.
vstinner reacted with thumbs up emoji vstinner reacted with heart emoji vstinner reacted with rocket emoji
All reactions
  • 👍 1 reaction
  • ❤️ 1 reaction
  • 🚀 1 reaction

Sorry, something went wrong.

Uh oh!

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

vstinner
vstinner approved these changes Oct 4, 2023
View reviewed changes

ghost 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. I just left a few more minor comments.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Include/unicodeobject.h Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Outdated
# CRASHES equaltoutf8(b'abc', b'abc')
# CRASHES equaltoutf8([], b'abc')
# CRASHES equaltoutf8(NULL, b'abc')
# CRASHES equaltoutf8('abc') # NULL

ghost Oct 4, 2023

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.

Oh ok, I thought that they were just pseudo-code as comments. Sure, you can leave # NULL if you prefer.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Modules/_testcapi/unicode.c Resolved

Uh oh!

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

@bedevere-app bedevere-app Bot added awaiting core review and removed awaiting review labels Oct 4, 2023
@encukou

ghost commented Oct 5, 2023 •
edited by encukou
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

IMO we ned a general strategy around dealing with strings. Let's not solve just for PyUnicode_Equal, but design something that we'll also use for, say, dict and attribute lookup.

Having two functions, for for both zero-terminated str and for separate length argument, sounds good to me. And we also want a third one that takes PyUnicode. (Yes, in this case we have it already).

Which of those should be in what kind of C-API? Which should be in stable ABI, which can just be inline functions? What should the naming conventions be? Is the char* const? What's the thread safety strory?
Please delay merging until after the sprint -- I hope to come up with a proposal for how to answer questions like that, consistently.

erlend-aasland 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

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

Which of those should be in what kind of C-API?

The 3 flavors should be exposed as regular function calls.

pitrou and erlend-aasland 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.

@vstinner

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

This is 2023 and null-encoded C strings are definitely not a good idea for new C APIs.

Would you mind to elaborate why/how using null terminated C string became a bad thing in 2023?

All reactions

Sorry, something went wrong.

Uh oh!

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

@pitrou

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

Would you mind to elaborate why/how using null terminated C string became a bad thing in 2023?

"Became a bad thing in 2023" is your interpretation. It has always been a design mistake, but it becomes even more glaring when interoperating with other languages which made the correct decision (that is, strings in those languages store their size explicitly).

In the distant times when the CPython C API was only called from C software, expecting null-terminated strings was fine, but it's not anymore.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

In the distant times when the CPython C API was only called from C software, expecting null-terminated strings was fine, but it's not anymore.

I don't thin that it's worth to argue. We should just add an API without size, and an API with a size. That's all.

The API without size is at least needed to upgrade all users of _PyUnicode_Equal() and _PyUnicode_EqualToASCIIId(), removed in Python 3.13.

erlend-aasland 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.

@pitrou

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

I don't thin that it's worth to argue.

Ah... I'm reassured, thank you.

vstinner and erlend-aasland reacted with heart emoji
All reactions
  • ❤️ 2 reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

ghost commented Oct 5, 2023

Copy link
Copy Markdown
Member

Oh, apparently this PR is now discussed at https://discuss.python.org/t/new-pyunicode-equaltoutf8-function/35377

All reactions

Sorry, something went wrong.

Uh oh!

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

davidhewitt
davidhewitt reviewed Oct 5, 2023
View reviewed changes
Comment thread Objects/unicodeobject.c Outdated
s += 4;
}
}
return *s == 0;

ghost Oct 5, 2023

Copy link
Copy Markdown
Contributor

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.

I suppose that if we return true at this point then we know that str is the utf8 representation of unicode, does it make sense to copy the contents into unicode->utf8 so that future operations can fast-path without needing to encode again?

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 5, 2023

Copy link
Copy Markdown
Member Author

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.

It needs a separate research and discussion. The disadvantage is that it increases the consumed memory size, also it consumes some CPU time, so the benefit will be only if the UTF-8 cache is used in future.

If the idea turned out to be good, it can simply be implemented in the future.

Sorry, something went wrong.

Uh oh!

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

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

ghost Oct 5, 2023 •
edited by davidhewitt
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

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.

Makes total sense. I guess this also sits in an awkward place where it's likely that the user is best suited to know whether or not they want the utf-8 cache populated, but it's also an implementation detail that we don't really want to expose to users. For now I'll just mark this comment as resolved. Edit I can't, probably lack permissions I guess.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 5, 2023

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.

PyUnicode_EqualToUTF8() doesn't raise exception and cannot fail. Trying to allocate memory should not raise memory, but it sounds like a non-trivial side effect.

Worst case: 1 GB string, you call PyUnicode_EqualToUTF8() and suddenly, Python allocates 1 GB more. I would be surprised by this behavior.

Sorry, something went wrong.

Uh oh!

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

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

ghost Oct 5, 2023

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.

Maybe it's worth it to add a comment explaining why we don't cache the UTF-8 encoded string.

Sorry, something went wrong.

Uh oh!

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

All reactions
Serhiy Storchaka and others added 2 commits October 5, 2023 22:31
Serhiy Storchaka Antoine Pitrou
Apply suggestions from code review
ee5781d
Co-authored-by: Antoine Pitrou <[email protected]>
Serhiy Storchaka
Add PyUnicode_EqualToUTF8AndSize().
1a4eb7b
vstinner
vstinner reviewed Oct 6, 2023
View reviewed changes
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Doc/c-api/unicode.rst


.. c:function:: int PyUnicode_EqualToUTF8(PyObject *unicode, const char *string)
.. c:function:: int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *string, Py_ssize_t size)

ghost Oct 6, 2023

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.

What do you think about renaming string to utf8_str? The utf8_ would be another way to document that it's expected to be encoded to UTF-8 and also it's easier (for me) to distinguish that the second argument is a bytes string, since string name is quite generic.

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 7, 2023

Copy link
Copy Markdown
Member Author

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.

It is a part of the bigger issue. See #62897.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Doc/c-api/unicode.rst Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Objects/unicodeobject.c Resolved

Uh oh!

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

Serhiy Storchaka and others added 4 commits October 7, 2023 15:43
Serhiy Storchaka Victor Stinner
Apply suggestions from code review
b124377
Co-authored-by: Victor Stinner <[email protected]>
Serhiy Storchaka
Add more parentheses.
029f1a0
Serhiy Storchaka
Remove redundant arguments.
be2ffe8
Serhiy Storchaka
Merge branch 'main' into capi-PyUnicode_EqualToString
29b26f7
vstinner
vstinner approved these changes Oct 7, 2023
View reviewed changes

ghost 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 the updated PR which now also adds PyUnicode_EqualToUTF8AndSize(). You just have to fix the merge conflict.

So far, I didn't see any real blocker issue in the healthy discussion.

Sorry, something went wrong.

Uh oh!

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

All reactions
erlend-aasland
erlend-aasland previously requested changes Oct 10, 2023
View reviewed changes
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

Serhiy Storchaka added 2 commits October 10, 2023 23:34
Serhiy Storchaka
Turn docstrings into comments.
78de49d
Serhiy Storchaka
Merge branch 'main' into capi-PyUnicode_EqualToString
fc79d5e
@erlend-aasland
erlend-aasland dismissed their stale review October 10, 2023 20:46

Offending docstrings were removed; dismissing my request for changes

@serhiy-storchaka

ghost commented Oct 11, 2023

Copy link
Copy Markdown
Member Author

@pitrou, does it look good to you now?

All reactions

Sorry, something went wrong.

Uh oh!

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

pitrou
pitrou reviewed Oct 11, 2023
View reviewed changes
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

pitrou
pitrou reviewed Oct 11, 2023
View reviewed changes
Comment thread Lib/test/test_capi/test_unicode.py Resolved

Uh oh!

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

pitrou
pitrou reviewed Oct 11, 2023
View reviewed changes
Comment thread Misc/stable_abi.toml
[function.PyUnicode_EqualToUTF8]
added = '3.13'
[function.PyUnicode_EqualToUTF8AndSize]
added = '3.13'

ghost Oct 11, 2023

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.

Unrelated question, but is there a plan to generate this file from Doc/data/stable_abi.dat or the reverse?

Sorry, something went wrong.

Uh oh!

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

All reactions

ghost Oct 11, 2023

Copy link
Copy Markdown
Member Author

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.

I think that Doc/data/stable_abi.dat is generated from Misc/stable_abi.toml.

Sorry, something went wrong.

Uh oh!

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

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

ghost Oct 11, 2023

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.

Ah, ok, thank you!

Sorry, something went wrong.

Uh oh!

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

All reactions
Serhiy Storchaka
Add tests for empty strings.
19ad126
@serhiy-storchaka
serhiy-storchaka merged commit eb50cd3 into python:main Oct 11, 2023
@bedevere-app bedevere-app Bot removed the awaiting core review label Oct 11, 2023
@serhiy-storchaka
serhiy-storchaka deleted the capi-PyUnicode_EqualToString branch October 11, 2023 13:42
@vstinner

ghost commented Oct 12, 2023

Copy link
Copy Markdown
Member

I added these functions to pythoncapi-compat: python/pythoncapi-compat@99ab0d3

All reactions

Sorry, something went wrong.

Uh oh!

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

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

Reviewers

@gpshead gpshead gpshead left review comments
@vstinner vstinner vstinner approved these changes
@pitrou pitrou pitrou requested changes
@erlend-aasland erlend-aasland erlend-aasland left review comments
@encukou encukou Awaiting requested review from encukou encukou is a code owner
+1 more reviewer
@davidhewitt davidhewitt davidhewitt left review comments
Reviewers whose approvals may not affect merge requirements

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.

7 participants

@serhiy-storchaka @vstinner @gpshead @encukou @pitrou @davidhewitt @erlend-aasland
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.