AttributeError: 'str' object has no attribute 'X'
1 · The lesson
readWhat this error means
You asked a string for a method it doesn't have. The method name in the message tells you what you thought you were holding: append means you expected a list, decode means bytes, get or keys means a dict, is_integer means a number. The variable is a str, and the bug is almost never in the line that crashed — it's wherever that value stayed text when you expected it to become something else.
When you see it
Traceback (most recent call last): File "main.py", line 2, in <module> names.append("Grace") ^^^^^^^^^^^^ AttributeError: 'str' object has no attribute 'append'
Python 3.13 sometimes adds a hint when a string method has a similar name:
Traceback (most recent call last): File "main.py", line 2, in <module> text = raw.decode("utf-8") ^^^^^^^^^^ AttributeError: 'str' object has no attribute 'decode'. Did you mean: 'encode'?
Take that hint with a pinch of salt. encode exists on str, but it's the opposite operation and rarely what you want here.
Why it happens
Four situations cover nearly every case.
1. You wanted a list and started with a string. Someone wrote names = "Ada" meaning "a list with Ada in it". Strings are immutable, so they have no append, remove or insert.
names = "Ada" names.append("Grace") # AttributeError: no attribute 'append'
2. Python 2 habits: .decode() on text. In Python 3, str is already decoded text. Only bytes has .decode(). Code ported from Python 2, or copied from an old answer, calls .decode("utf-8") on something that's already a str.
3. A number that's still text. input() always returns a string. So does every cell that csv.reader hands you. The value looks like 42, but it's "42".
quantity = "3" # what input() or csv.reader actually gives you print(quantity.is_integer()) # AttributeError: no attribute 'is_integer'
4. JSON you never parsed. An API body, a message off a queue or a value from a database column comes back as a JSON string. Until you call json.loads, it's characters, not a dict.
body = '{"customer": "Hedy", "total": 42.5}' print(body.get("customer")) # AttributeError: no attribute 'get'
Try body["customer"] instead and you get a different error — TypeError: string indices must be integers, not 'str' — for exactly the same reason.
How to fix it
Start with the right type. If it's meant to hold several names, make it a list from the beginning:
names = ["Ada"] names.append("Grace") print(names) # ['Ada', 'Grace']
Building up a string rather than a list? Use += or, better, collect the pieces in a list and join once at the end:
parts = [] for name in ["Ada", "Grace", "Linus"]: parts.append(name) print(", ".join(parts)) # Ada, Grace, Linus
Drop the .decode(). If the value is already a str, there's nothing to decode. If you genuinely might get either, check the type once at the boundary:
def as_text(value): if isinstance(value, bytes): return value.decode("utf-8") return value print(as_text(b"Ada Lovelace")) # Ada Lovelace print(as_text("Grace Hopper")) # Grace Hopper
Convert numbers where they come in. Do it once, at the line that reads the value, not three functions later:
import csv import io sales_csv = io.StringIO("item,qty,price\nkeyboard,2,49.99\nmouse,5,19.50\n") total = 0.0 for row in csv.DictReader(sales_csv): qty = int(row["qty"]) # "2" -> 2 price = float(row["price"]) # "49.99" -> 49.99 total += qty * price print(f"Revenue: {total:.2f}") # Revenue: 197.48
Parse JSON before you use it. json.loads turns the string into dicts and lists:
import json body = '{"customer": "Hedy", "total": 42.5}' order = json.loads(body) print(order.get("customer")) # Hedy print(type(order)) # <class 'dict'>
When you're not sure what you've got, print type(x) and repr(x). repr shows the quotes, so '42' versus 42 is obvious at a glance.
When you'd actually see this in real code
- A config value read from an environment variable:
os.environ["MAX_RETRIES"]is always a string, soretries.bit_length()or any arithmetic on it fails. - A
requestscall where you usedresp.textinstead ofresp.json()and then tried.get("data")on the result. - A JSON column in SQLite or a Redis value: you stored it with
json.dumps, read it back, and forgot thejson.loadson the way out. - A function that accepts "one name or a list of names" and gets passed a bare string — then
names.append(...)blows up, or worse,for n in namesquietly loops over letters.
Related errors
- AttributeError: 'list' object has no attribute 'X' — the mirror image: a list where you expected one string.
- AttributeError: 'dict' object has no attribute 'X' — the step after this one: you parsed the JSON, then tried dot access on the dict.
- TypeError: unsupported operand type(s) for +: 'int' and 'str' — the same text-versus-number mix-up, caught by arithmetic instead of a method call.
- ValueError: invalid literal for int() with base 10 — what happens when the text you convert isn't a clean number.
- TypeError: 'str' object does not support item assignment — strings can't be changed in place either.
- json.JSONDecodeError: Expecting value — when
json.loadsitself fails.
See Also
- All Python errors — the full index, by type and by when it happens.
- Working with Strings (tutorial)
- String methods cheat sheet
- CSV and JSON
- Input and output
Found this useful? Share it with a friend who’s learning Python.