AttributeError: 'X' object has no attribute 'y' means Python looked up .y on an object of type X and didn’t find it. That message gives you two things, and the first one is the more useful: the actual type of the object. Most AttributeErrors happen because the object isn’t the type you thought it was.

Traceback (most recent call last):
  File "report.py", line 9, in <module>
    print(match.group(1))
          ^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'group'

If you expected a re.Match and the message says NoneType, the bug is wherever match was assigned, not on this line.

For module 'x' has no attribute 'y' (a module rather than an object), the causes are different. See Fix AttributeError: module has no attribute.

TLDR

Look at the type in 'X' object has no attribute 'y'. It’s usually not the type you expected:

  • NoneType: x = x.sort(), inplace=True, re.match with no match, missing return. Fix: don’t assign in-place results; check for None where the value came from.
  • dict: JSON data accessed with dots. Fix: data["name"], or parse into a dataclass/pydantic model.
  • str / list: a parse step skipped, or find_all() instead of find(). Fix: parse first, or take one item.
  • your own class: def indented inside __init__, __int__ typo, attribute set only on some paths. Fix: correct the indentation or name, and set every attribute in __init__.

On Python 3.10+, a Did you mean: '...'? at the end of the message is usually the answer.

Python may already suggest the fix

Since Python 3.10, a misspelled attribute gets a suggestion at the end of the message:

AttributeError: 'list' object has no attribute 'appnd'. Did you mean: 'append'?

If you see Did you mean, that’s almost always the answer. The suggestion is printed in the traceback but isn’t part of str(e), so it won’t show up in logs that only record the exception message.

‘NoneType’ object has no attribute

The variable holds None. The fix is never on the line that failed; trace the variable back to where it got its value. These are the usual sources:

Methods that modify in place return None. Assigning their result replaces your object with None:

items = [3, 1, 2]
items = items.sort()      # sort() returns None
items.pop()               # 'NoneType' object has no attribute 'pop'

df = df.dropna(inplace=True)   # same with pandas inplace=True
df.head()                      # 'NoneType' object has no attribute 'head'

Use items.sort() without assignment, or items = sorted(items). With pandas, drop either the assignment or inplace=True.

Lookups that return None for “not found”. re.match, re.search, dict.get, os.environ.get, BeautifulSoup’s find(), Django’s .first(), and SQLAlchemy’s .first() / session.get() all return None instead of raising. Check the result before using it:

m = re.search(r"order-(\d+)", line)
if m is None:
    continue
order_id = m.group(1)

A function that forgot to return. A function that falls off the end returns None. If the null value came from your own function, check every branch of it for a missing return.

‘dict’ object has no attribute

JSON gives you dicts, and dict values aren’t attributes:

data = response.json()
data.name        # 'dict' object has no attribute 'name'
data["name"]     # works

If you want attribute access, convert the payload once into a dataclass or a pydantic model rather than wrapping it in SimpleNamespace everywhere. A related message, 'dict' object has no attribute 'iteritems' or 'has_key', means the code was written for Python 2: use .items() and key in d.

‘str’ or ‘list’ object has no attribute

A str where you expected something richer usually means a step was skipped: the JSON string was never parsed (json.loads), a date was never parsed (datetime.fromisoformat), or a file path was passed where a file object was expected. A list where you expected a single item usually comes from an API that returns many results: BeautifulSoup’s find_all() versus find(), a query’s .all() versus .first(), or a JSON array at the top level of a response.

Print type(obj) and repr(obj) just before the failing line; the mismatch is usually obvious from that.

The method exists, but Python says it doesn’t

This is the case people find most confusing: you can see the method in the class, yet 'MyClass' object has no attribute 'save'. Common reasons:

The def is indented one level too deep. It’s nested inside __init__ (or another method), so it’s a local function, not a method:

class Report:
    def __init__(self, rows):
        self.rows = rows

        def save(self, path):      # inside __init__, not on the class
            ...

Report([]).save("out.csv")
# AttributeError: 'Report' object has no attribute 'save'

Mixing tabs and spaces, or pasting code from elsewhere, makes this easy to miss. Your editor’s outline view shows which methods the class really has.

__init__ is misspelled, so it never runs. def __int__(self) or def _init_(self) defines a different method, and the attributes it sets never exist:

class User:
    def __int__(self, name):     # typo: __int__
        self.name = name

User().name    # 'User' object has no attribute 'name'

The attribute is only set on some code paths. If self.results is assigned inside an if or in a method that hasn’t been called yet, it doesn’t exist until then. Assign every attribute in __init__, even if only to None or [].

Double-underscore names are mangled. self.__token inside class Client is stored as _Client__token, so client.__token from outside fails. Use a single underscore for “internal” attributes.

The call is on a different object. A variable named the same as your class or module can shadow it, or a decorator can return a wrapper instead of your class. print(type(obj), obj.__class__.__module__) confirms what you actually have.

Setting an attribute can fail too

AttributeError is also raised on assignment:

class Point:
    __slots__ = ("x", "y")

Point().z = 1
# AttributeError: 'Point' object has no attribute 'z' and no __dict__ for setting new attributes

Classes with __slots__ (and frozen dataclasses, which raise FrozenInstanceError, a subclass of AttributeError) don’t accept new attributes. Assigning to a @property without a setter fails with property 'x' of 'C' object has no setter on Python 3.11+ and can't set attribute on older versions.

Checking for an attribute

hasattr(obj, "name") and getattr(obj, "name", default) let you check without raising. They’re the right tool when objects legitimately differ (optional plugin hooks, duck typing across versions of a library). They’re the wrong tool for hiding an unexpected None: getattr(user, "email", None) turns a missing user into a missing email, and the real bug surfaces later and further away.

Type hints plus a checker (mypy, pyright, or your IDE) catch most of the cases on this page before the code runs, especially Optional[...] values used without a None check.

Reading the traceback

The last line tells you the type and name; the frame just above it tells you where the attribute was accessed. When the AttributeError happens inside a library (a Django template, a pandas accessor, an ORM), the useful frame is the last one in your own code, where you passed in the wrong object. Debugly’s stack trace formatter highlights your own frames and filters out the library ones, so that line is easy to find in a long traceback.