# Hasattr() performance on objects with \_\_slots\_\_

**URL:** <https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134>\
**Category:** Python Help\
**Tags:** help\
**Created:** [September 19, 2026, 4:55am UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134 "2026-09-19T04:55:13Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![crj](https://avatars.discourse-cdn.com/v4/letter/c/df705f/32.png) [@crj](https://discuss.python.org/u/crj)\
**Post date:** [September 19, 2026, 4:55am UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134/1 "2026-09-19T04:55:13Z")

</div>

For a particular application I was wondering whether to represent a state by the absence of an attribute, or by the attribute being `None`. Curious, I decided to benchmark it, and the result surprised me a lot:

`hasattr()` is very slow on specifically a _slot_ that has no value.

Really, very _very_ slow. I created two classes:

```python
class C1:
    __slots__ = ('x',)

class C2:
    pass

```

…and benchmarked `hasattr(obj, 'x')` for both of them in the case where x was and was not set:

- `C1`, `hasattr`→`False`: **242 ns**
- `C1`, `hasattr`→`True`: 30 ns
- `C2`, `hasattr`→`False`: 33 ns
- `C2`, `hasattr`→`True`: 31 ns

So that’s a slowdown by a factor of 7-8. Does anybody know why this is the case? Is it a bug?

Any insight would be gratefully appreciated!

* * *

### Supplementary observations:

Testing was on a local build of 3.14.0 under x86\_64 Ubuntu, but I replicated the phenomenon under Ubuntu’s builds of 3.13.0 and 3.12.7 .

`getattr(obj, 'x', sentinel) is not sentinel` is a consistent 20ns slower than `hasattr(obj, 'x')` across the board: the same spike is observed for a slot that has no value.

Conversely, `try` / `getattr(obj, 'x')` / `catch AttributeError` is slow any time the exception path is taken, for obvious reasons, but there is _not_ an additional slowdown for a slot.

It makes very little difference what other slots the object has, whether or not it has a ` __dict__ ` slot, what other attributes there are in slots and/or ` __dict__ `, etc.

Down in the C code, the key difference seems to be whether `PyObject_GetOptionalAttr()` or `PyObject_GetAttr()` gets called. I observed that the implementation of the former looks very much more complicated than the latter, but then realised I’d gone way too far down a rabbit hole and backed out.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [September 19, 2026, 9:11pm UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134/2 "2026-09-19T21:11:37Z")

</div>

When an object has ` __slots__ `, attributes are populated ahead of time with a special descriptor object. For example:

```pycon
>>> import inspect
>>> class Test:
... __slots__ = ('x',)
...     
>>> inspect.getattr_static(Test(), 'x')
<member 'x' of 'Test' objects>

```

When actually looking up the attribute, Python then has to invoke ` __get__ `, which in this case means formatting and constructing an `AttributeError` object, which the other paths don’t have to do (because they have fast paths that know when they’re being used under `hasattr`), thus causing the apparent slowdown.

I suppose we can optimize this by adding a special case in `_PyObject_GenericGetAttrWithDict` that avoids invoking `tp_descr_get` when looking up an exact `PyMemberDescr_Type` instance. Please file an issue for this on [our issue tracker](https://github.com/python/cpython/issues).

---

<div class="post-metadata">

**Author:** ![crj](https://avatars.discourse-cdn.com/v4/letter/c/df705f/32.png) [@crj](https://discuss.python.org/u/crj)\
**Post date:** [September 20, 2026, 6:08am UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134/3 "2026-09-20T06:08:15Z")

</div>

Thanks; [done](https://github.com/python/cpython/issues/157840).

I guess you’re right and the special case is the easy win. It feels a little inelegant, but anything cleaner would involve an API change…

---

<div class="post-metadata">

**Author:** ![crj](https://avatars.discourse-cdn.com/v4/letter/c/df705f/32.png) [@crj](https://discuss.python.org/u/crj)\
**Post date:** [September 20, 2026, 8:31am UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134/4 "2026-09-20T08:31:37Z")

</div>

Actually, are C extensions allowed to call `tp_descr_get` directly? If not, maybe its spec could be augmented to permit returning `NULL` without setting an exception, to indicate the attribute was not found. That could give a clean separation of concerns. 🤔

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [September 20, 2026, 3:49pm UTC](https://discuss.python.org/t/hasattr-performance-on-objects-with-slots/109134/5 "2026-09-20T15:49:32Z")

</div>

Yes, C extensions are allowed to call `tp_descr_get`. (In fact, I don’t think there’s any way to invoke ` __get__ ` without doing so.)
