ITADN
PyO3/pyo3/Issues

`getattr_opt` uses identity check instead of subclass check for `AttributeError` on Python < 3.13

#5984OpenHarikeshav-R 创建于 2026-04-18
H
Harikeshav-Rcommented
`PyAnyMethods::getattr_opt` on Python < 3.13 uses `.is()` (type identity) instead of `is_subclass_of` to check for `AttributeError`. This means that subclasses of `AttributeError` are not treated as "attribute not found" and are instead propagated as errors. On Python 3.13+, `getattr_opt` uses the C API `PyObject_GetOptionalAttr`, which correctly handles `AttributeError` subclasses. The behavior difference between Python versions is unexpected. This was discovered via a regression in pydantic (pydantic/pydantic#13092), where Django's `RelatedObjectDoesNotExist` (a subclass of `AttributeError`) is raised by a property and should be treated as a missing attribute. The bug is in `src/types/any.rs`, in the `#[cfg(not(Py_3_13))]` branch of `getattr_opt`: ```rust #[cfg(not(Py_3_13))] { match any.getattr(attr_name) { Ok(bound) => Ok(Some(bound)), Err(err) => { let err_type = err .get_type(any.py()) .is(PyType::new::<PyAttributeError>(any.py())); // ^^^ identity check — `type(err) is AttributeError` // should be a subclass check — `isinstance(err, AttributeError)` match err_type { true => Ok(None), false => Err(err), } } } } ``` `.is()` returns `true` only when the error type is exactly `AttributeError`, not when it's a subclass. The fix should use `is_subclass_of::<PyAttributeError>()` (or equivalent) to match the semantics of the Python 3.13+ code path. ### Steps to Reproduce 1. Define a Python class with a property that raises an `AttributeError` subclass: ```python class MissingRelation(AttributeError): pass class Obj: @property def child(self): raise MissingRelation("missing child") ``` 2. Call `getattr_opt` on an instance of `Obj` for the `child` attribute (from Rust via PyO3). 3. On Python < 3.13: `getattr_opt` returns `Err(MissingRelation)` instead of `Ok(None)`. 4. On Python 3.13+: `getattr_opt` correctly returns `Ok(None)`. ### Backtrace ```shell N/A — no panic, just incorrect error propagation. ``` ### Your operating system and version macOS 26.3.1 (arm64) ### Your Python version (`python --version`) Python 3.12.x (reproduces on any Python < 3.13) ### Your Rust version (`rustc --version`) rustc 1.87.0 ### Your PyO3 version 0.28.3 ### How did you install python? Did you use a virtualenv? pyenv, with virtualenv ### Additional Info The suggested fix is to change the `#[cfg(not(Py_3_13))]` fallback in `getattr_opt` from: ```rust let err_type = err.get_type(any.py()).is(PyType::new::<PyAttributeError>(any.py())); ``` to something like: ```rust let err_type = err.get_type(any.py()).is_subclass_of::<PyAttributeError>()?; ``` This would make the pre-3.13 fallback match the semantics of `PyObject_GetOptionalAttr` on Python 3.13+. Upstream pydantic issue: pydantic/pydantic#13092 Pydantic PR that exposed this: pydantic/pydantic#12571
0 条评论