| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
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
Verification screenshotsBuilt CPython from source and ran the REPL to verify before/after behavior. Before (Python 3.13, no cross-language hints)After (this PR)All 447 tests in test_traceback pass (including 28 new cross-language tests). |
Sorry, something went wrong.
|
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! |
Sorry, something went wrong.
|
Thanks for taking this on! From a language perspective, I think these can be shorter. Instead of: AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead of '.push'? I think it is enough to say: AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead? or even: AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'? |
Sorry, something went wrong.
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:
Sorry, something went wrong.
| # | ||
| # 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.
Sorry, something went wrong.
There was a problem hiding this comment.
Done, switched to the GH issue link.
Sorry, something went wrong.
| (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").
Sorry, something went wrong.
There was a problem hiding this comment.
Makes sense, dropped the alignment.
Sorry, something went wrong.
| ``"".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``. |
Sorry, something went wrong.
There was a problem hiding this comment.
Applied your suggestion.
Sorry, something went wrong.
| 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?
Sorry, something went wrong.
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.
Sorry, something went wrong.
- 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 Did you mean '.append'? in 6d58cdc. 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. |
Sorry, something went wrong.
|
@ZeroIntensity re: suggesting language constructs - added both in 6d58cdc. They use the custom hint format since they suggest constructs rather than method equivalents: >>> {}.put("a", 1)
AttributeError: 'dict' object has no attribute 'put'. Use dict[key] = value for item assignment
>>> [].contains(1)
AttributeError: 'list' object has no attribute 'contains'. Use 'x in list' to check membership
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. |
Sorry, something went wrong.
appreciat you digging in when you're ready! ::anxiously waits:: |
Sorry, something went wrong.
|
Oh, you also need to add an entry to "What's New in Python 3.15". |
Sorry, something went wrong.
|
Added in 579d037 - covers the basic hint format and the language-construct variant (dict.put, list.contains). |
Sorry, something went wrong.
- 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).
There was a problem hiding this comment.
LGTM. The implementation is correct and well tested. IMO the feature makes sense and is useful.
Sorry, something went wrong.
| # 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.
Sorry, something went wrong.
There was a problem hiding this comment.
Applied in f702e79 - wrapped in types.MappingProxyType.
Sorry, something went wrong.
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.
Sorry, something went wrong.
There was a problem hiding this comment.
Nice - frozendict is cleaner than the MappingProxyType wrapper. Thanks for applying it.
Sorry, something went wrong.
There was a problem hiding this comment.
It seems like the frozendict was lost in the meanwhile.
Sorry, something went wrong.
|
I applied my suggestion to replace types.MappingProxyType with frozendict. |
Sorry, something went wrong.
|
@ZeroIntensity @picnixz: Are you ok with this change? |
Sorry, something went wrong.
There was a problem hiding this comment.
One nit and otherwise LGTM. I think @serhiy-storchaka had some more reservations/suggestions so I'd wait a bit for his feedback maybe?
Sorry, something went wrong.
There was a problem hiding this comment.
Fine with me!
Sorry, something went wrong.
|
Thanks @picnixz for the review and approval! Happy to wait for @serhiy-storchaka's feedback. Is the nit you mentioned something I should address now, or was it from the earlier round (the Levenshtein point and the raw hint formatting)? Those should be fixed in the latest commits but let me know if I missed something. |
Sorry, something went wrong.
|
The nit is just adding a newline before the "1.". |
Sorry, something went wrong.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Added the blank line in 0089761. Waiting on @serhiy-storchaka whenever he has time. |
Sorry, something went wrong.
|
See my comments on the issue. First, it should work for subclasses. We should also fix suggestions for float.__or__ and other wrong suggestions. Add suggestions for tuple.append, frozenset.add, frozendict.update, etc. Maybe add some suggestions for None. |
Sorry, something went wrong.
…e, float - Use isinstance() instead of type(obj) for hint lookup, so subclasses of builtin types (e.g. OrderedDict, custom list subclasses) also get cross-language hints. - Restructure table to index by method name for efficient isinstance iteration. - Add mutable-on-immutable hints: tuple.append/extend/insert/remove suggest list, frozenset.add/discard/remove/update suggest set, frozendict.update suggests dict. - Add NoneType hints: common methods (keys, upper, sort, etc.) tried on None suggest the type the user likely expected. - Add float bitwise hints: __or__/__and__/__xor__/__lshift__/__rshift__ suggest using int, fixing the misleading __dir__ Levenshtein suggestion. - Cross-language hints now take priority over Levenshtein (they are more specific; Levenshtein still fires as fallback when no table match). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks @serhiy-storchaka for the thorough feedback! Addressed all your points in e2c12ec: Subclass support: Switched from type(obj) exact match to isinstance() check. OrderedDict().entries() now suggests .items(), custom list subclasses get hints too. Table restructured to index by method name for efficient isinstance iteration. Mutable on immutable: Added tuple.append/extend/insert/remove (suggests list), frozenset.add/discard/remove/update (suggests set), frozendict.update (suggests dict). None suggestions: Added hints for common methods tried on None: None.keys() suggests dict, None.upper() suggests str, None.sort() suggests list, etc. Float bitwise: Added float.__or__/__and__/__xor__/__lshift__/__rshift__ suggesting int. Cross-language hints now take priority over Levenshtein (more specific), so the misleading __dir__ suggestion is replaced. Levenshtein still fires as fallback when no table match exists. Demo (built from source, Python REPL): All 433 tests in test_traceback pass. |
Sorry, something went wrong.
| "__lshift__": [(float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True)], | ||
| "__rshift__": [(float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True)], | ||
| # NoneType -- common methods tried on None (got None instead of expected type) | ||
| "keys": [(type(None), "Did you expect a 'dict'?", True)], |
There was a problem hiding this comment.
You can replace type(None) with types.NoneType.
Sorry, something went wrong.
|
|
||
| .. doctest:: | ||
|
|
||
| >>> None.keys() # doctest: +ELLIPSIS |
There was a problem hiding this comment.
I don't understand the None suggestions. Yes, None has no keys() method, but I don't see how I'm supposed to replace it with a dict in this example.
I expected an example where a variable is set to None by mistake, such as:
>>> lst=[1,2,3].sort()
>>> lst.pop()
Sorry, something went wrong.
| # 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.
It seems like the frozendict was lost in the meanwhile.
Sorry, something went wrong.
Add missing @force_not_colorized decorator to three cross-language test methods. The CPython C-level traceback path includes ANSI color codes in error output, causing assertEndsWith to fail when the expected string doesn't account for trailing escape sequences. Also add the decorator to test_cross_language_levenshtein_fallback and test_cross_language_no_hint_for_unknown_attr for consistency with all other cross-language tests.
1. Restore frozendict() wrapper for _CROSS_LANGUAGE_HINTS table (lost during the isinstance() refactor in e2c12ec). Convert inner lists to tuples for consistency with immutable container. 2. Replace type(None) with types.NoneType per review. 3. Replace contrived None.keys() doctest with a realistic example: lst = [3, 1, 2].sort() followed by lst.pop(), showing the common "method returned None" mistake vstinner suggested.
|
Addressed all three in e1eb6f4:
>>> lst = [3, 1, 2].sort()
>>> lst.pop()This shows the real scenario (method returned None instead of the expected object). Also updated the prose to explain why. (Also fixed the CI failure from the previous push in 564ca3a - three cross-language tests were missing @force_not_colorized.) |
Sorry, something went wrong.
There was a problem hiding this comment.
LGTM.
Sorry, something went wrong.
| "__xor__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),), | ||
| "__lshift__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),), | ||
| "__rshift__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),), | ||
| # NoneType -- common methods tried on None (got None instead of expected type) |
There was a problem hiding this comment.
I'm not convinced that suggestions on None are useful, but @serhiy-storchaka suggested them. For me, they sound a little bit too generic.
Sorry, something went wrong.
There was a problem hiding this comment.
Well, let's remove them. Most of these methods can actually be defined in several classes, for example str, bytes and bytearray, or list and dict, or dict and frozendict.
Sorry, something went wrong.
There was a problem hiding this comment.
Ok, I removed suggestions on None.
Sorry, something went wrong.
Documentation build overview2 files changed ± whatsnew/3.15.html ± whatsnew/changelog.html |
Sorry, something went wrong.
|
Merged, thanks for your contribution to Python. |
Sorry, something went wrong.
|
@vstinner was a great experience. Thank you and team |
Sorry, something went wrong.
|
Awesome to see! But shouldn't all the mutable methods be included? The first thing I tried was .clear(), which was missing from frozendict, frozenset, and tuple. I think it would be best to check all the missing methods and add all that make sense, it's a small list for each: >>> set(dir(dict)) - set(dir(frozendict))
{'clear', 'pop', 'update', '__ior__', 'setdefault', 'popitem', '__setitem__', '__delitem__'}
>>> set(dir(list)) - set(dir(tuple))
{'__imul__', 'sort', '__reversed__', 'clear', 'pop', 'insert', 'copy', 'reverse', '__iadd__', 'extend', '__setitem__', 'append', '__delitem__', 'remove'}
>>> set(dir(set)) - set(dir(frozenset))
{'__isub__', '__iand__', 'clear', 'pop', 'update', '__ior__', 'difference_update', 'intersection_update', 'symmetric_difference_update', 'add', '__ixor__', 'discard', 'remove'} |
Sorry, something went wrong.
|
I'm not sure that it's worth to add suggestions for all methods, but adding clear() sounds reasonable. @mvanhorn: Do you want to propose a PR adding clear() suggestions? |
Sorry, something went wrong.
|
@vstinner sure, happy to. I'll open a follow-up PR adding clear() to the cross-language hints table for tuple, frozenset, and frozendict (each suggesting the mutable counterpart, same shape as the existing append/discard/update entries). Aiming for this week. Thanks @henryiii for the catch. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Summary
When an AttributeError on a builtin type (list, str, dict) has no
Levenshtein-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):
>>> [1, 2, 3].push(4) AttributeError: 'list' object has no attribute 'push' >>> 'hello'.toUpperCase() AttributeError: 'str' object has no attribute 'toUpperCase' >>> {}.keySet() AttributeError: 'dict' object has no attribute 'keySet' >>> [].add(1) AttributeError: 'list' object has no attribute 'add'After (this PR):
>>> [1, 2, 3].push(4) AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead of '.push'? >>> 'hello'.toUpperCase() AttributeError: 'str' object has no attribute 'toUpperCase'. Did you mean '.upper' instead of '.toUpperCase'? >>> {}.keySet() AttributeError: 'dict' object has no attribute 'keySet'. Did you mean '.keys' instead of '.keySet'? >>> [].add(1) AttributeError: 'list' object has no attribute 'add'. Did you mean to use a set? Sets have an .add() methodLevenshtein 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
Evidence
Fixes #146406