GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-146406: Add cross-language method suggestions for builtin AttributeError by mvanhorn · Pull Request #146407 · python/cpython Conversation
|
| Source | Evidence |
|---|---|
| SO: push vs append | 320K views |
| SO: trim whitespace | 1.5M views |
| JetBrains/PSF Survey 2024 | 40% of Python devs also use JS |
| CPython precedent | elseif->elif (gh-132449), import x from y->from x import y (gh-98931) |
| Discourse thread | 420 views, 25 likes, 3 core devs supportive |
Fixes #146406
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
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.
There was an error while loading. Please reload this page.
|
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: |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| # | ||
| # See https://discuss.python.org/t/106632 for the design discussion. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Let's link to the GH issue instead.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Done, switched to the GH issue link.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| (list, "push"): "append", | ||
| (list, "concat"): "extend", |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, dropped the alignment.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| ``"".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.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Applied your suggestion.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| 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.
The reason will be displayed to describe this comment to others. Learn more.
Hm, what needs to be suppressed here?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
There was an error while loading. Please reload this page.
- 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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
appreciat you digging in when you're ready! ::anxiously waits:: |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Oh, you also need to add an entry to "What's New in Python 3.15". |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Added in 579d037 - covers the basic hint format and the language-construct variant ( |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
57b8fc8 to
8a39e32
Compare
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
- 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
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
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
There was an error while loading. Please reload this page.
…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.
The reason will be displayed to describe this comment to others. Learn more.
LGTM. The implementation is correct and well tested. IMO the feature makes sense and is useful.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| # 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.
The reason will be displayed to describe this comment to others. Learn more.
You might use a frozendict for such global constant dictionary.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Applied in f702e79 - wrapped in types.MappingProxyType.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Nice - frozendict is cleaner than the MappingProxyType wrapper. Thanks for applying it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
It seems like the frozendict was lost in the meanwhile.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I applied my suggestion to replace |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@ZeroIntensity @picnixz: Are you ok with this change? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Fine with me!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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.
There was an error while loading. Please reload this page.
|
The nit is just adding a newline before the "1.". |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
Added the blank line in 0089761. Waiting on @serhiy-storchaka whenever he has time. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
See my comments on the issue. First, it should work for subclasses. We should also fix suggestions for |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…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) <[email protected]>
|
Thanks @serhiy-storchaka for the thorough feedback! Addressed all your points in e2c12ec: Subclass support: Switched from Mutable on immutable: Added None suggestions: Added hints for common methods tried on None: Float bitwise: Added Demo (built from source, Python REPL): All 433 tests in test_traceback pass. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| "__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.
The reason will be displayed to describe this comment to others. Learn more.
You can replace type(None) with types.NoneType.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| .. doctest:: | ||
|
|
||
| >>> None.keys() # doctest: +ELLIPSIS |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
There was an error while loading. Please reload this page.
| # 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.
The reason will be displayed to describe this comment to others. Learn more.
It seems like the frozendict was lost in the meanwhile.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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 (Also fixed the CI failure from the previous push in 564ca3a - three cross-language tests were missing |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| "__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.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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 an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Ok, I removed suggestions on None.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Documentation build overview
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Merged, thanks for your contribution to Python. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@vstinner was a great experience. Thank you and team |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Awesome to see! But shouldn't all the mutable methods be included? The first thing I tried was >>> 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.
There was an error while loading. Please reload this page.
|
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.
There was an error while loading. Please reload this page.
|
@vstinner sure, happy to. I'll open a follow-up PR adding |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Add cross-language method suggestions for builtin AttributeError
There was an error while loading. Please reload this page.