Skip to content

Use tp_getattro for AttributeError suggestions instead of the __getattr__ hook#126

Open
jhonabreul wants to merge 2 commits into
QuantConnect:masterfrom
jhonabreul:attribute-error-suggestions-tp-getattro
Open

Use tp_getattro for AttributeError suggestions instead of the __getattr__ hook#126
jhonabreul wants to merge 2 commits into
QuantConnect:masterfrom
jhonabreul:attribute-error-suggestions-tp-getattro

Conversation

@jhonabreul

Copy link
Copy Markdown
Collaborator

Summary

Reimplements the AttributeError member-name suggestions (added in #124, v2.0.55) on top of a ClassObject.tp_getattro override, and removes the miss-only __getattr__ hook. Behavior is unchanged — a missing attribute on a .NET object still gets a snake_case Did you mean ...? hint:

>>> System.String("x").lenght
AttributeError: 'String' object has no attribute 'lenght' Did you mean: 'length'?

Version bumped to 2.0.56.

Why replace the hook

#124 installed a shared __getattr__ on every reflected type and manually rewired tp_getattro to CPython's private slot_tp_getattr_hook (address scraped from a probe class). That is a lot of low-level CPython surgery on the type system, done for one reason: to keep the successful attribute-access ("hit") path free of the small per-access cost that a tp_getattro override adds.

Benchmarking showed that cost is not worth the complexity:

  • The per-access overhead of the tp_getattro override is ~17–19 ns/access.
  • On realistic Lean backtests and a 50-algorithm regression subset it was within run-to-run noise (< 0.1% aggregate) — only a pathological loop dominated by raw C# property reads (~117M accesses) showed a measurable ~9%.

Given the negligible real-world impact, this PR drops the slot manipulation entirely and keeps the enrichment in a straightforward tp_getattro that delegates to PyObject_GenericGetAttr and only does work on a miss.

Diff vs master

 src/runtime/AttributeErrorHint.cs | 109 ------  (removed: hook + manual slot wiring)
 src/runtime/PythonEngine.cs       |   7 ---     (no Initialize/Shutdown hook calls)
 src/runtime/TypeManager.cs        |   4 --      (no per-type install)
 src/runtime/Types/ClassBase.cs    |  73 +--      (inline suggestion building)
 src/runtime/Types/ClassObject.cs  |  15 ++       (tp_getattro override)

DynamicClassObject continues to enrich on its own attribute-miss path (unchanged). Suggestions are ranked by case-insensitive Levenshtein distance + substring containment, dunder names are skipped, and member names are emitted in snake_case (const/static-readonly → UPPER_CASE) via the existing ToSnakeCase helpers.

Testing

  • Python tests (tests/test_class.py) and embedding tests (src/embed_tests/TestPropertyAccess.cs) — unchanged from Suggest similar member names on AttributeError for .NET objects #124 and passing.
  • Full Lean QuantConnect.Tests Python/Pandas unit suite and all Python/* regression algorithms were previously run against this feature with zero regressions vs master (all failures pre-existing/environmental).

🤖 Generated with Claude Code

jhonabreul and others added 2 commits July 1, 2026 18:06
Reimplement the AttributeError member-name suggestions on top of a
ClassObject.tp_getattro override, replacing the miss-only __getattr__
hook (AttributeErrorHint) that was merged in QuantConnect#124.

The hook installed a shared __getattr__ on every reflected type and
manually rewired tp_getattro to CPython's slot_tp_getattr_hook. That
surgery is significantly more invasive for no measurable real-world
benefit: the per-access cost it avoids (~17 ns) is lost in the noise on
realistic Lean workloads. This version keeps the enrichment entirely in
a tp_getattro override that delegates to PyObject_GenericGetAttr and only
does work on a miss, and drops all the slot manipulation.

Behaviour and messages are unchanged (snake_case "Did you mean ...?"
suggestions); the existing Python and embedding tests are untouched and
still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant