PythonMastery
reference 3 min read · lesson 57 of 60 in Errors

AttributeError: 'dict' object has no attribute 'X'

1 · The lesson

read

What this error means

You used dot access — user.name — on a dictionary. Python dicts don't expose their keys as attributes; you reach a value with user["name"] or user.get("name"). The other common cause is a Python 2 method name, such as iteritems or has_key, that was removed in Python 3.

When you see it

Dot access on data that came from JSON:

python
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    print(user.name)
          ^^^^^^^^^
AttributeError: 'dict' object has no attribute 'name'

Old Python 2 code running on Python 3:

python
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    for item, qty in stock.iteritems():
                     ^^^^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'iteritems'

Why it happens

Dot access is for attributes, brackets are for keys. JavaScript blurs the two — user.name and user["name"] are the same thing there — so it's an easy habit to bring over. Python keeps them apart. A dict's attributes are its methods (keys, items, get, update…). Its data lives behind the brackets.

python
import json

response = '{"name": "Grace", "role": "admin"}'
user = json.loads(response)
print(user.name)            # AttributeError: no attribute 'name'

There's a nastier variant: when the key happens to share a name with a dict method, dot access doesn't fail at all. It hands you the method.

python
order = {"id": 1042, "items": ["keyboard", "mouse"]}
print(order.items)          # <built-in method items of dict object at 0x...>
print(order["items"])       # ['keyboard', 'mouse']

No error, wrong value. That alone is a good reason not to try to make dicts dot-accessible.

Python 2 method names. These were removed in Python 3:

Python 2Python 3
d.iteritems()d.items()
d.iterkeys()d.keys() or just d
d.itervalues()d.values()
d.has_key(k)k in d
d.viewitems()d.items() (it's already a view)

In Python 3, items(), keys() and values() return lightweight views, not copies, so the iter* versions had no reason to stay.

How to fix it

Use brackets when the key must be there, .get() when it might not. A missing key with brackets raises KeyError, which is what you want for required fields. .get() returns None, or a default you choose:

python
import json

user = json.loads('{"name": "Grace", "role": "admin"}')
print(user["name"])                     # Grace
print(user.get("team", "unassigned"))   # unassigned

Translate the Python 2 calls:

python
stock = {"keyboard": 12, "mouse": 40, "monitor": 3}

for item, qty in stock.items():
    print(f"{item:<10}{qty:>4}")

if "mouse" in stock:
    print("mouse in stock")

If you want dot access, convert to a real object — on purpose. For a quick script, types.SimpleNamespace turns a dict into something with attributes. object_hook applies it to every nested object while parsing:

python
import json
from types import SimpleNamespace

response = '{"name": "Grace", "address": {"city": "Arlington", "zip": "22201"}}'
user = json.loads(response, object_hook=lambda d: SimpleNamespace(**d))
print(user.name)            # Grace
print(user.address.city)    # Arlington

For anything that lives longer than a script, define the shape with a dataclass. You get dot access, a readable repr, and an error at construction time if a field is missing — instead of a KeyError buried three calls deep:

python
import json
from dataclasses import dataclass

@dataclass
class Invoice:
    number: str
    customer: str
    total: float

payload = '{"number": "INV-2041", "customer": "Linus", "total": 312.40}'
invoice = Invoice(**json.loads(payload))
print(invoice.customer)     # Linus
print(invoice)              # Invoice(number='INV-2041', customer='Linus', total=312.4)

When the data comes from an API you don't control, a validation library such as pydantic does the same job and also checks the types. For your own code, a dataclass is usually enough.

I'd steer clear of the "AttrDict" recipes that subclass dict and override __getattr__. They work until a key is called items, keys, copy or update, or contains a hyphen, and then you're back to brackets anyway.

When you'd actually see this in real code

  • requests.get(url).json() returns plain dicts and lists. Code written against a typed SDK (response.data.id) breaks when you swap to raw HTTP.
  • A Flask or Django view reads request.json.email instead of request.json["email"].
  • Code ported from Python 2 — old tutorials, inherited scripts — still calls .iteritems() or .has_key().
  • A YAML or TOML config loaded with yaml.safe_load or tomllib: both give you dicts, so config.database.host fails.
  • A function that used to return an object was refactored to return a dict, and one caller still uses dot access.

See Also

Found this useful? Share it with a friend who’s learning Python.