Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-106307: Add PyMapping_GetOptionalItem() #106308

Merged
merged 10 commits into from
Jul 11, 2023

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Jul 1, 2023

Replacement of PyObject_GetItem() which doesn't raise KeyError.

int _PyMapping_LookupItem(PyObject *obj, PyObject *key, PyObject **result)

Return 1 and set *result != NULL if a key is found.
Return 0 and set *result == NULL if a key is not found; a KeyError is silenced.
Return -1 and set *result == NULL if an error other than KeyError is raised.

Replacement of PyObject_GetItem() which doesn't raise KeyError.

  int _PyMapping_LookupItem(PyObject *obj, PyObject *key, PyObject **result)

Return 1 and set *result != NULL if a key is found.
Return 0 and set *result == NULL if a key is not found;
a KeyError is silenced.
Return -1 and set *result == NULL if an error other than KeyError
is raised.
@serhiy-storchaka serhiy-storchaka force-pushed the _PyMapping_LookupItem branch from cfc5851 to 078ea48 Compare July 1, 2023 20:46
Include/cpython/object.h Outdated Show resolved Hide resolved
Python/import.c Outdated
_PyErr_Clear(tstate);
}
}
(void)_PyMapping_LookupItem(modules, name, &m);
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of interest, why not explicitly _PyErr_Clear(tstate) if _PyMapping_LookupItem returns -1?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because the current code does not clear arbitrary errors (and doing this would be wrong). It only clears KeyError, what is done internally by _PyMapping_LookupItem.

@serhiy-storchaka serhiy-storchaka requested review from a team and encukou as code owners July 7, 2023 17:15
@serhiy-storchaka serhiy-storchaka changed the title gh-106307: Add _PyMapping_LookupItem() gh-106307: Add PyMapping_GetOptionalItem() Jul 7, 2023
Copy link
Member

@carljm carljm left a comment

Choose a reason for hiding this comment

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

The bytecodes.c cleanups alone (i.e. the existing cases where we cared enough about perf to special-case the exact-dict fast path) clearly show the value of this API! Now all call sites can get the same perf benefit, with much less verbosity.

Doc/c-api/mapping.rst Outdated Show resolved Hide resolved
Doc/c-api/mapping.rst Outdated Show resolved Hide resolved
Doc/whatsnew/3.13.rst Outdated Show resolved Hide resolved
Include/abstract.h Outdated Show resolved Hide resolved
Include/abstract.h Outdated Show resolved Hide resolved
Doc/c-api/mapping.rst Outdated Show resolved Hide resolved
Comment on lines 40 to 43
Return ``1`` and set ``*result != NULL`` if a key is found.
Return ``0`` and set ``*result == NULL`` if a key is not found;
a :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
Copy link
Member

@carljm carljm Jul 7, 2023

Choose a reason for hiding this comment

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

To me, "KeyError is silenced" suggests internal implementation details that the caller doesn't need to care about, and b) may or may not be true (in the exact-dict case, no KeyError is silenced because it isn't set in the first place). IMO "no KeyError is raised" is a clearer statement of the contract that is relevant to the caller.

Suggested change
Return ``1`` and set ``*result != NULL`` if a key is found.
Return ``0`` and set ``*result == NULL`` if a key is not found;
a :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
Return ``1`` and set ``*result != NULL`` if the key is found.
Return ``0`` and set ``*result == NULL`` if the key is not found;
the :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than

Copy link
Member

Choose a reason for hiding this comment

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

On second thought, it may be important to specify "the KeyError is silenced" to clarify the behavior with a custom __getitem__ method that raises KeyError explicitly. I take back that part of the suggestion; I would just replace all three instances of a with the.

Copy link
Member

Choose a reason for hiding this comment

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

I dislike specify that *pvalue is set to NULL on error. I would prefer to leave it undefined.

Code like (void)PyMapping_GetOptionalItem(modules, name, &m); would be unsafe: I prefer to handle explicitly the error case and no rely on m value.

In the implementation, I prefer to set it to NULL to avoid... the undefined behavior :-) Maybe in debug mode we could even set the value to a marker value to detect the usage of the undefined value.

Copy link
Member Author

Choose a reason for hiding this comment

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

I disagree. It will require explicitly setting result = NULL before every call of this function, and if you forgot to do this, it will crash your code in rare case of error, which may be not covered by tests. The risk of writing errorenous code is too high even if there would be a benefit from not setting the result to NULL.

Also, it makes the code simpler. Instead of saving the return code into a variable and checking it in two if\ s

int rc = PyMapping_GetOptionalItem(...);
if (rc < 0) {
    goto error;
}
if (rc == 0) {
    // handle default
}

you can get rid of that variable and insert the call directly in the if.

if (PyMapping_GetOptionalItem(...) < 0) {
    goto error;
}
if (value == NULL) {
    // handle default
}

And there are many other cases in which it is convenient that you have two ways to check for result. I suggest you to make PyMapping_GetOptionalItem() not setting the result to NULL on error and to look how much code it will require to rewrite and how much uglier it will become. It is the same for PyObject_GetOptionalAttr.

Copy link
Contributor

@erlend-aasland erlend-aasland Jul 11, 2023

Choose a reason for hiding this comment

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

I disagree. It will require explicitly setting result = NULL before every call of this function, and if you forgot to do this, it will crash your code in rare case of error, which may be not covered by tests.

+1 and slightly related, I have a PR for the devguide repo regarding explicitly clearing output parameters: python/devguide#1129 python/devguide#1128

cc. @serhiy-storchaka, @carljm

Doc/whatsnew/3.13.rst Outdated Show resolved Hide resolved
Objects/abstract.c Show resolved Hide resolved
Objects/abstract.c Outdated Show resolved Hide resolved
Objects/abstract.c Outdated Show resolved Hide resolved
Modules/_pickle.c Outdated Show resolved Hide resolved
Comment on lines 40 to 43
Return ``1`` and set ``*result != NULL`` if a key is found.
Return ``0`` and set ``*result == NULL`` if a key is not found;
a :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
Copy link
Member

Choose a reason for hiding this comment

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

I dislike specify that *pvalue is set to NULL on error. I would prefer to leave it undefined.

Code like (void)PyMapping_GetOptionalItem(modules, name, &m); would be unsafe: I prefer to handle explicitly the error case and no rely on m value.

In the implementation, I prefer to set it to NULL to avoid... the undefined behavior :-) Maybe in debug mode we could even set the value to a marker value to detect the usage of the undefined value.

@brettcannon brettcannon removed their request for review July 7, 2023 22:01
serhiy-storchaka and others added 3 commits July 8, 2023 11:45
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Carl Meyer <carl@oddbird.net>
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

LGTM

Misc/stable_abi.toml Outdated Show resolved Hide resolved
@serhiy-storchaka serhiy-storchaka merged commit 4bf4371 into python:main Jul 11, 2023
@serhiy-storchaka serhiy-storchaka deleted the _PyMapping_LookupItem branch July 11, 2023 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants