# Make \_PyObject\_LookupAttr public

**URL:** <https://discuss.python.org/t/make-pyobject-lookupattr-public/29104>\
**Category:** Core Development\
**Created:** [July 6, 2023, 4:12pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104 "2023-07-06T16:12:59Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [July 6, 2023, 4:12pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/1 "2023-07-06T16:12:59Z")

</div>

In [Speed up and clean up getting optional attributes in C code · Issue #76752 · python/cpython · GitHub](https://github.com/python/cpython/issues/76752) I added private C API `_PyObject_LookupAttr` as a convenient function for getting optional attribute which combines `PyObject_GetAttr()`, `PyErr_ExceptionMatches(PyExc_AttributeError)` and `PyErr_Clear()`.

```c
int _PyObject_LookupAttr(PyObject *obj, PyObject *attr_name, PyObject **result);

```

It returns one of three values, 0, -1 or 1 and stores the result or NULL in `*result`.

- Return 1 and set `*result != NULL` if an attribute is found.
- Return 0 and set `*result == NULL` if an attribute is not found;  
an AttributeError is silenced.
- Return -1 and set `*result == NULL` if an error other than AttributeError  
is raised.

It allows to replace the following code

```c
PyObject *result = PyObject_GetAttr(obj, attr_name);
if (result == NULL) {
    if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
        PyErr_Clear();
        // handle "no such attribute" case
    }
    else {
        // handle other errors
    }
}
else {
    // handle "has attribute" case
}

```

as

```c
PyObject *result;
if (_PyObject_LookupAttr(obj, attr_name, &result) < 0) {
    // handle other errors
}
else if (result == NULL) {
    // handle "no such attribute" case
}
else {
    // handle "has attribute" case
}

```

Advantages of using this function:

- It simplifies the code, especially if “no such attribute” and “has attribute” cases are handles the same.
- Due to this, it allowed to fix incorrect code which ignored other errors or treated them the same as AttributeError.
- It avoids raising and catching the AttributeError exception at first place, if the attribute is looked up in object’s ` __dict__ `.

Currently this function is used around 350 times in the CPython code. I think that it can be useful for use in third-party code too, because it makes easier to write correct code. While the function can be re-implemented using other public C API (`PyObject_GetAttr()`, `PyErr_ExceptionMatches()`, `PyExc_AttributeError` and `PyErr_Clear()`), it will not provide the performance gain.

Now, I have a question about the name. I chose different verb to differentiate from `PyObject_GetAttr`, but I am not sure that it is the best option. I considered other variants with `Find`, `Search`, `GetOpt`, `TryGet`, etc. `Lookup` looked good to me, but it would conflict with `_PyType_Lookup()` and `_PyObject_LookupSpecialId()` if we made them public too. Whatever name we choose, we should perhaps use the same verb in names of other similar functions (getting optional item in mapping [2] and specifically dict [3]).

Do you support adding such function to the public C API? What is the best name fot it and for similar functions?

[1] [Speed up and clean up getting optional attributes in C code · Issue #76752 · python/cpython · GitHub](https://github.com/python/cpython/issues/76752)  
[2] [Add \_PyMapping\_LookupItem() · Issue #106307 · python/cpython · GitHub](https://github.com/python/cpython/issues/106307)  
[3] [C API: Add PyDict\_GetItemRef() function · Issue #106004 · python/cpython · GitHub](https://github.com/python/cpython/issues/106004#issuecomment-1615600709)

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [July 6, 2023, 4:30pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/2 "2023-07-06T16:30:44Z")

</div>

About the interface, I modeled it (returning a tri-state code for “success”, “not found” and “error” and storing the result by the provided address) by `_PyObject_GetMethod()` and some static functions. `PyIter_Send()` added later was modeled by the same scheme.

It allows to avoid using `PyErr_Occurred()` which adds some overhead to differentiate between “not found” and “error” cases.

---

<div class="post-metadata">

**Author:** ![guido](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/guido/32/21_2.png) [@guido](https://discuss.python.org/u/guido)\
**Post date:** [July 6, 2023, 4:52pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/3 "2023-07-06T16:52:29Z")

</div>

Maybe call it `PyObject_GetOptionalAttr`?

This feels similar to `getattr(obj, “attr”, None)` in pure Python, except with a unique singleton default. There is no special verb there, so maybe we should consider it as a modifier here too.

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [July 6, 2023, 10:07pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/4 "2023-07-06T22:07:31Z")

</div>

I’m in favor of making the API public, I like the proposed API (error cases, ignoring AttributeError, etc.). I have no preference for the method name.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [July 7, 2023, 8:21am UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/5 "2023-07-07T08:21:55Z")

</div>

Would not it be confusing that its interface is so different from other `Get` functions which returns `PyObject*`. `_PyObject_LookupAttr` replaced `_PyObject_GetAttrWithoutError` which was the same as `PyObject_GetAttr`, but did not raise AttributeError. The users can expect this from `PyObject_GetOptionalAttr`. I searched for a new verb to emphasize the fact that it not only returns an optional value, but has a completely different interface.

If `PyObject_GetOptionalAttr` does not cause confusion, would it be good to name other function `PyMapping_GetOptionalItem`?

And what about the name for a new dict-specific function? Victor initially proposed name `PyDict_GetItemRef`, but it does not look descriptive to me (suffix `Ref` has very different meaning in other names). `PyDict_GetOptionalItem` would be in line with `PyMapping_GetOptionalItem`, but existing `PyDict_GetItemWithError` already returns an _optional_ item, the only difference is that it returns a _borrowed_ reference.

---

<div class="post-metadata">

**Author:** ![seberg](https://avatars.discourse-cdn.com/v4/letter/s/71e660/32.png) [@seberg](https://discuss.python.org/u/seberg)\
**Post date:** [July 7, 2023, 8:36am UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/6 "2023-07-07T08:36:22Z")

</div>

Just a 👍, this would be useful both for speed and probably also small simplification in NumPy. I somewhat like the `Optional` suggestion in the name, the different signatures are a bit of a downer, but to me they seem different enough in practice to be obvious when reading code (and an obvious error when writing).

One similar function is: `PyContextVar_Get` which uses `Get`. A difference is that it also has the `*default_value` as input.

In NumPy practically every function call has to go through `getattr(obj, "special_attr", NULL)` in C; I only see such dispatching schemes to increase in the future.  
We do skip this for most builtin Python types and our own ones, so it isn’t usually a performance hit in practice. But there are examples where it is a significant overhead (I know one example where that overhead contributed to downstream moving to lower overhead but subtly incorrect functions instead – although NumPy should provide new API to avoid that also).

(I am a little curious if there might be a way to allow these fast-paths for custom getattr’s. But we don’t use them for the moment, even if I can see that changing at some point.)

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [July 7, 2023, 4:37pm UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/7 "2023-07-07T16:37:58Z")

</div>

Maybe I am overthinking this. Here is an issue:

> <https://github.com/python/cpython/issues/106521>
>
> It is a new name of former \`\_PyObject\_LookupAttr()\` added in #76752.
> 
> \`\`\`c
> in…t PyObject\_GetOptionalAttr(PyObject \*obj, PyObject \*attr\_name, PyObject \*\*result);
> \`\`\`
> 
> Discussion about making it public and naming: https://discuss.python.org/t/make-pyobject-lookupattr-public/29104.
> 
> See also other new functions with similar interface in #106307 and #106004.
> 
> 
> \### Linked PRs
> \* gh-106522

---

<div class="post-metadata">

**Author:** ![gpshead](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gpshead/32/54_2.png) [@gpshead](https://discuss.python.org/u/gpshead)\
**Post date:** [July 8, 2023, 12:06am UTC](https://discuss.python.org/t/make-pyobject-lookupattr-public/29104/8 "2023-07-08T00:06:18Z")

</div>

While ironically previous decade meme worthy… We _could_ adopt a convention of appending `Maybe` to such API names going forward: `PyObject_GetAttrMaybe`

Such a name does imply to the reader that the result needs checking before use.
