gh-146406: Add cross-language method suggestions for builtin AttributeError#146407
gh-146406: Add cross-language method suggestions for builtin AttributeError#146407mvanhorn wants to merge 9 commits intopython:mainfrom
Conversation
When Levenshtein-based suggestions find no match for an AttributeError
on list, str, or dict, check a static table of common method names from
JavaScript, Java, C#, and Ruby.
For example, [].push() now suggests .append(), "".toUpperCase() suggests
.upper(), and {}.keySet() suggests .keys().
The list.add() case suggests using a set instead of suggesting .append(),
since .add() is a set method and the user may have passed a list where
a set was expected (per discussion with Serhiy Storchaka, Terry Reedy,
and Paul Moore).
Design: flat (type, attr) -> suggestion text table, no runtime
introspection. Only exact builtin types are matched to avoid false
positives on subclasses.
Discussion: https://discuss.python.org/t/106632
|
I will make a review of this PR when I have time (by the end of the week), so fellow core devs, please hold off any merge, TiA! |
|
Thanks for taking this on! From a language perspective, I think these can be shorter. Instead of: I think it is enough to say: or even: |
ZeroIntensity
left a comment
There was a problem hiding this comment.
Looks very cool!
It'd also be nice if we could suggest using language constructs in some cases. For example:
dict.put->dict[x] = ylist.contains->x in list
Lib/traceback.py
Outdated
| # | ||
| # See https://discuss.python.org/t/106632 for the design discussion. |
There was a problem hiding this comment.
Let's link to the GH issue instead.
There was a problem hiding this comment.
Done, switched to the GH issue link.
Lib/traceback.py
Outdated
| (list, "push"): "append", | ||
| (list, "concat"): "extend", |
There was a problem hiding this comment.
I'd prefer to keep standard convention and just use a single space after the :. Alignment adds extra maintenance (because we need to change each entry if we add something that breaks it) and also creates a false-symmetry between each entry (e.g., for our purposes (list, "push"): "append" has no functional relation to (list, "concat"): "extend").
There was a problem hiding this comment.
Makes sense, dropped the alignment.
| ``"".toUpperCase()`` suggests ``upper``. The ``list.add()`` case suggests | ||
| using a set instead, following feedback from the community discussion. |
There was a problem hiding this comment.
This last sentence isn't particularly useful:
| ``"".toUpperCase()`` suggests ``upper``. The ``list.add()`` case suggests | |
| using a set instead, following feedback from the community discussion. | |
| ``"".toUpperCase()`` suggests ``upper``. |
There was a problem hiding this comment.
Applied your suggestion.
Lib/traceback.py
Outdated
| else: | ||
| self._str += f". Did you mean '.{suggestion}' ({suggestion!a}) instead of '.{wrong_name}' ({wrong_name!a})?" | ||
| elif hasattr(exc_value, 'obj'): | ||
| with suppress(Exception): |
There was a problem hiding this comment.
Hm, what needs to be suppressed here?
There was a problem hiding this comment.
Nothing realistic - _get_cross_language_hint is just a dict lookup on (type(obj), wrong_name), both of which are already validated by the time we get here. I had it as a defensive measure since we're inside traceback formatting, but on reflection it's unnecessary given how simple the function is. Removed it.
- Shorten hint format to "Did you mean '.append'?" (drop redundant "instead of '.push'" since the error already names the attribute) - Add dict.put and list.contains entries suggesting language constructs (dict[key] = value, x in list) per @ZeroIntensity's review - Replace suppress(Exception) with direct call (function is safe) - Link to GH issue instead of Discourse thread in comment - Drop column alignment in hint table entries - Trim NEWS entry last sentence
|
Good call - the error message already says "has no attribute 'push'" so repeating it in the hint is noise. Shortened to I kept the existing Levenshtein format as-is (that's a separate code path and probably a separate discussion), so the cross-language hints now have their own slighty shorter style. |
|
@ZeroIntensity re: suggesting language constructs - added both in 6d58cdc. They use the custom hint format since they suggest constructs rather than method equivalents: The existing architecture already handles this - entries with a space in the hint string are rendered as-is rather than wrapped in "Did you mean" format. |
appreciat you digging in when you're ready! ::anxiously waits:: |
|
Oh, you also need to add an entry to "What's New in Python 3.15". |
|
Added in 579d037 - covers the basic hint format and the language-construct variant ( |
57b8fc8 to
8a39e32
Compare
- Use (hint, is_raw) tuples instead of space-based raw detection - Shorten list.add hint to "Did you mean to use a 'set' object?" - Use d[k] = v instead of dict[key] = value for dict.put hint - Add dict.entries -> items (JavaScript) - Remove Levenshtein guardrail from code comment (belongs on issue) - Add periods to raw hint messages - Add test for dict.entries
Address review feedback from @vstinner: - Merge 14 individual test_cross_language_* methods into a single parameterized test_cross_language using subTest - Shorten raw-message hints: "Use 'x in list'." and "Use d[k] = v." - Fix pre-existing levenshtein priority test assertion - Update whatsnew entry to match shortened hint text
…suffix matching Apply vstinner's review suggestion: use assertEndsWith instead of assertIn for more precise test assertions. Split cases into method hints (checked via Did you mean pattern) and raw hints (checked via exact suffix).
vstinner
left a comment
There was a problem hiding this comment.
LGTM. The implementation is correct and well tested. IMO the feature makes sense and is useful.
Lib/traceback.py
Outdated
| # If is_raw is True, the suggestion is rendered as-is. | ||
| # | ||
| # See https://github.com/python/cpython/issues/146406. | ||
| _CROSS_LANGUAGE_HINTS = { |
There was a problem hiding this comment.
You might use a frozendict for such global constant dictionary.
There was a problem hiding this comment.
Applied in f702e79 - wrapped in types.MappingProxyType.
There was a problem hiding this comment.
You don't need types.MappingProxyType. We have a native frozendict type in 3.15 as of PEP 814.
| # If is_raw is True, the suggestion is rendered as-is. | ||
| # | ||
| # See https://github.com/python/cpython/issues/146406. | ||
| _CROSS_LANGUAGE_HINTS = types.MappingProxyType({ |
There was a problem hiding this comment.
I was thinking at the new built-in frozendict type:
| _CROSS_LANGUAGE_HINTS = types.MappingProxyType({ | |
| _CROSS_LANGUAGE_HINTS = frozendict({ |


Summary
When an
AttributeErroron a builtin type (list,str,dict) has noLevenshtein-based suggestion, check a static table of common method names
from other languages.
Before:
After:
Discourse discussion
Discussed at https://discuss.python.org/t/106632 (420 views, 25 likes, 15 posts, 3 core devs).
Design decisions from the thread:
Flat table per @pf_moore (post 14, 4 likes):
list.add() suggests set, not append per @Storchaka (post 8):
Reinforced by @tjreedy (post 12):
Scope guardrails per @dr_carlos (post 3): only add entries backed by real confusion evidence, not just because methods are similar.
Static table for builtins only (Option 1) - community consensus. 11 entries covering JavaScript, Java, C#, and Ruby.
Verification
Before (system Python 3.13, no cross-language hints):
After (this PR):
Levenshtein still takes priority (trim->strip, indexOf->index already work and are not in the table). Only exact builtin types are matched - subclasses are not affected.
Changes
Lib/traceback.py: Added_CROSS_LANGUAGE_HINTSdict (11 entries),_get_cross_language_hint()function, and a 4-line fallback hook inTracebackException.__init__that runs only when Levenshtein found nothing.Lib/test/test_traceback.py: 15 test cases covering all entries, priority ordering, unknown attrs, and subclass exclusion.Misc/NEWS.d/: NEWS entry.Evidence
elseif->elif(gh-132449),import x from y->from x import y(gh-98931)Fixes #146406