Cross-language method suggestions for AttributeError

Hi - I’ve had a couple PRs merged into CPython recently (defaultdict repr fix, man page wrapping fix) and have been poking around the error suggestion system. Found something worth talking about.

Python’s “Did you mean?” works great for typos. items.appndappend. But it doesn’t help when the wrong name comes from another language.

Try this:

[1, 2, 3].push(4)

You get:

AttributeError: 'list' object has no attribute 'push'

No suggestion. push and append are too far apart for Levenshtein to match. Same story with str.toUpperCase() (Python: upper()), str.trim() (Python: strip()), dict.keySet() (Python: keys()).

This actually happens

Stack Overflow threads where people hit this exact AttributeError:

The search patterns tell the same story. “Why do python lists have pop() but not push?” has 320K views. “How do I trim whitespace?” has 1.5M views - people are searching for trim, not strip.

The JetBrains/PSF Developer Survey 2024: 40% of Python-primary developers also use JavaScript. 64% of web-focused Python devs use JS. That’s a lot of people carrying method-name habits from other ecosystems.

PEP 616 (removeprefix/removesuffix) exists because of this exact class of confusion. From the PEP:

“There have been repeated issues on Python-Ideas, Python-Dev, the Bug Tracker, and StackOverflow, related to user confusion about the existing str.lstrip and str.rstrip methods.”

MIT and the University of Washington maintain explicit Java-to-Python method mapping tables for students. List.add(e)list.append(e), toLowerCaselower, indexOffind. Universities building translation cheatsheets tells you the gap is structural.

CPython already does this for SyntaxError

This isn’t new territory. CPython has been adding cross-language suggestions for years:

  • print "hello"print("hello") (Python 2 migration, Nick Coghlan 2014, moved to PEG parser by Pablo Galindo 2021)
  • exec "code"exec("code") (same mechanism)
  • elseifelif (C/JS/PHP keyword, Pablo Galindo 2025, gh-132449)
  • import x from yfrom x import y (JS syntax, Pablo Galindo 2022, gh-98931)

All cases where CPython detects syntax from another language and points you to the Python way. Extending this from SyntaxError to AttributeError feels like a natural next step.

The proposal

A fallback in the existing suggestion system: when Levenshtein finds no match, check a mapping of common method names from other languages to their Python equivalents.

For builtins:

# list
"push" -> "append"       # JS, Ruby
"add" -> "append"         # Java, C#
"addAll" -> "extend"      # Java
"indexOf" -> "index"      # JS, Java

# str
"toUpperCase" -> "upper"  # JS, Java
"toLowerCase" -> "lower"
"trim" -> "strip"
"trimStart" -> "lstrip"   # JS

# dict
"keySet" -> "keys"        # Java
"entrySet" -> "items"     # Java
"putAll" -> "update"      # Java

Typo suggestions always take priority. The cross-language lookup only runs when Levenshtein finds nothing.

Open question: extensibility

A static table only covers builtins. A few possible directions:

  1. Static table for builtins only. Small. Simple. Covers the highest-traffic cases. Similar to how print/exec detection is hardcoded for exactly 2 candidates.

  2. Registration API. Something like sys.register_attr_suggestion(type, wrong_name, right_name). Libraries register their own mappings. Adds API surface.

  3. Convention-based. A __attr_suggestions__ class attribute (dict mapping wrong names to right names). Follows the dunder pattern. Libraries opt in per-class, no global registry.

  4. Builtins now, extensibility later. Ship the static table. Design extensibility based on real usage data.

No strong preference here. Option 1 is the least controversial. Option 3 is interesting but might be over-engineering. Curious what others think.

What this doesn’t cover

  • Syntax equivalents like list.lengthlen(list) (different message format needed)
  • Operator confusion like +++= 1 (SyntaxError, not AttributeError)
  • Custom class support (see above)

Interested in hearing whether this is worth pursuing and which direction makes sense.

10 Likes

This sounds great to me. It only kicks in when the current behavior has nothing to suggest, so it’s a definite improvement.

4 Likes

Seems like an excellent suggestion, and a natural extension of the error message improvements we’ve been getting over the last few Python versions!

One thing to note is that there are only 4 custom SyntaxErrors from other languages, but your AttributeError list already has 11 items. This isn’t a bad thing, but I could see this getting out of hand quickly if there isn’t at least some simple guidelines around adding new items to the list (do we need a new way to spell ‘append’)?

So I’d say that option 1 is the best way to go:

If at some point in the future we find that extensibility is actually useful here, it can be added later.

Thanks Ned. Yeah the “only when current suggestions have nothing” part felt important - it’s purely additive. I have a sense of how this could fit into the existing suggestion machinery and would be interested in taking a crack at a PR if more people feel the same way.

Good call on the mapping count. I think the right bar is: only add an entry when there’s clear evidence of real confusion (SO traffic, survey data), not just because two methods do similar things. In practice that’s maybe 15-20 entries across list/str/dict/set, living in one place in the C source. Agreed on Option 1.

2 Likes

I like this idea though how far should we go?

When I first learned C++ we used std::list (and std::vector) which are sort of like a list in Python. Those have push_back for example. That’s more or less like append in Python. Similar idea with push_front(in std::list) and insert(0, X). We also have stuff like merge in C++ that can sort of be done with extend or list comprehension.

.. how far should we go? Is there a point where it’s too far?

1 Like

Note that most of the cited links are false positives. The only one that genuinely has the user using an equivalent Java method name in place of a Python one and got an AttributeError and it wasn’t really due to either an unexpected None or unrelated type confusion is for "string".contains() which requires more than just a mapping of names to accommodate.

It’s hard to say how many people really will see the AttributeErrors and how many will instead type my_list.pu<tab> in their IDEs, get zero hits and be just as in the dark as before.

(I still support the overall idea – at least if limited to the really obvious ones like list.push())

2 Likes

It can also mean that you passed a list instead of set. Changing “add” to “append” will not fix the error, it will introduce yet one error.

2 Likes

Good point. The list.add() case is tricky because it could mean “I have a list but expected a set” not “I’m coming from Java.”

The way I see it: this only fires when difflib.get_close_matches already came up empty. So set.add() works fine today and wouldn’t trigger anything. The suggestion would only appear for list.add() where there’s genuinely no close match on list.

To handle the ambiguity, the message could include the source language hint: did you mean 'append'? ('add' is the equivalent in Java/C#). That way if someone really did mean a set, the Java/C# context is a signal this isn’t their situation.

But if there are cases where even that framing is confusing, I’m happy to drop add -> append from the initial table entirely. Better to ship 12 unambiguous mappings than 15 with edge cases.

I’d keep it tight. JavaScript/Java/C#/Ruby cover the vast majority of “I just switched to Python” developers. C++ methods like push_back are possible but feel more niche.

The table is easy to extend later. Starting small and adding based on real confusion data (Stack Overflow questions, CPython issue reports) seems better than trying to cover everything upfront.

1 Like

Fair point on the SO links. You’re right that most of them are better explained by None values or type confusion than cross-language habits. The contains() one is the clearest genuine case. I should’ve been more careful with those citations.

That said, there is stronger evidence outside Stack Overflow:

Real AttributeError bugs in production repos: list.push() (acode/lib-python #3), str.startsWith() (falkTX/Carla #68), and another list.push() in Mozilla’s WebThingsIO/webthing-python #29. These are unambiguous cross-language confusion, not type errors.

On IDEs: Autocomplete helps when you don’t know the method name. It doesn’t help when you’re confident you know it. You’ve been writing JavaScript for three years. push is second nature. You type .push(), hit run, bare AttributeError, no suggestion. That’s the gap.

And a lot of Python happens outside IDEs entirely. Jupyter notebooks, Google Colab, Replit, terminal REPL. PEP 762 overhauled the built-in REPL for 3.13 because it’s still heavily used. IDLE ships with Python, recommended for beginners, no cross-language autocomplete. The JetBrains/PSF 2024 survey: 40% of Python-primary devs also use JavaScript.

On precedent: CPython already does this! Since 3.12, import os from sys gives you Did you mean 'from ... import ...'? That’s a JavaScript ES6 pattern getting a helpful redirect. Same idea here, just for AttributeError, and only when Levenshtein already found nothing.

On scope: agreed. Only mappings with a direct 1:1 equivalent, real confusion data backing them up, and too far from existing methods for Levenshtein to catch. ~15 entries total.

FWIW, IDLE prints exception messages as received from the interpreter. Hence,
NameError: name 'imt' is not defined. Did you mean: 'int'?.

When a method exists for another, related, Python class, I think it better to suggest the other class rather than assume a foreign language match. Hence list.add => (something like) “Did you mean to use a set?”.

1 Like

Thats a good point - the “wrong container” case is genuinely different from the cross-language muscle memory case. Someone who meant to use a set vs. someone who’s been writing Java all week.

I think they could layer together though. Check for a matching method on a related builtin type first, then fall back to the cross-language table if nothing fits. That way list.add gets “did you mean to use a set?” but list.push goes straight to the JS/Java hint.

I’m not sure there’s any need to check - if we’re going for option 1 (static table for builtins), the table can simply map the method name and object type onto a suggestion. So add on a list is mapped straight to “did you mean to use a set?” No need to check whether add is a set method - you know it is already.

Same for any of the registration options. Just let the user register the text of a suggestion, and don’t worry about trying to introspect what the user might have meant.

4 Likes

Good point - a flat table keeps it simple. Each (type, method) entry just carries the
right suggestion text, no layering needed… think this is ready for me to take a stab at? Sorry this is my first feature try, I’m a little overexcited!

2 Likes

Just submitted a PR on this feature, would love any feedback..

11 entries covering list/str/dict. Levenshtein takes priority - a few entries I originally planned (indexOf, trim) turned out to be redundant since Levenshtein already catches them. The list.add case suggests using a set per @storchaka’s feedback, and the whole thing is a flat table per @pf_moore’s suggestion - no introspection.

4 Likes