AttributeError: module 'x' has no attribute 'y' is a different problem from the ordinary 'X' object has no attribute 'y'. The module was imported successfully; it just doesn’t contain the name you asked for. That almost always means one of these:

  • the installed version of the library renamed or removed the name,
  • a circular import left the module half-initialized,
  • Python imported a different file than the library you meant (a local file with the same name, or the wrong package),
  • the name lives in a submodule that wasn’t imported.

For the object version of this error ('NoneType' object has no attribute, 'dict' object has no attribute), see Fix AttributeError in Python.

TLDR

Run this in the program that fails:

print(mod.__file__, mod.__version__, "name" in dir(mod))
  • traceback ends in the library’s __getattr__: name removed/renamed in this version. Fix: use the new name (e.g. np.float_ → np.float64) and pin the version.
  • partially initialized module: circular import. Fix: move shared code to a third module.
  • __file__ is inside your project: local file shadows the library. Fix: rename it, delete __pycache__.
  • wrong __version__ or path: another package or venv. Fix: pip show, check sys.executable.
  • module 'xml' has no attribute 'etree': submodule not imported. Fix: import xml.etree.ElementTree.

Start with three prints

Run these from the same program and environment that fails, not from a separate shell:

import numpy           # the module from the error
print(numpy.__file__)      # which file was actually imported
print(numpy.__version__)   # which version (most libraries define this)
print("float_" in dir(numpy))

__file__ answers the shadowing question, __version__ answers the version question, and dir() confirms whether the name exists. Between them they identify the cause in most cases.

The library version changed

This is the most common cause in practice: code written for one major version runs against another, often after a fresh install in CI or Docker picked up the latest release. The traceback ends inside the library’s own __getattr__, which many libraries use to give a better message for removed names:

Traceback (most recent call last):
  File "/app/train.py", line 3, in <module>
    labels = load_labels([1, 2])
  File "/app/data.py", line 4, in load_labels
    return np.array(rows, dtype=np.float_)
                                ^^^^^^^^^
  File "/app/.venv/lib/python3.13/site-packages/numpy/__init__.py", line 763, in __getattr__
    raise AttributeError(
    ...<3 lines>...
    )
AttributeError: `np.float_` was removed in the NumPy 2.0 release. Use `np.float64` instead.

The failing line is in your code (data.py, line 4); the frame below it is the library reporting that the name is gone. When the last frame is a __getattr__ with raise AttributeError(name) from None or similar, it’s the same situation: the library looked the name up for you and it doesn’t exist in this version.

The NumPy names people hit most often:

Name Status Use instead
np.int, np.float, np.object, np.str removed in 1.24 int, float, object, str, or np.int64 / np.float64
np.bool removed in 1.24, added back in 2.0 bool or np.bool_ (works in both)
np.long missing in late 1.x, added back in 2.0 as the C long type int or np.int64
np.float_, np.complex_, np.unicode_ removed in 2.0 np.float64, np.complex128, np.str_
np.NaN, np.Inf, np.NAN removed in 2.0 np.nan, np.inf
np.product, np.cumproduct removed in 2.0 np.prod, np.cumprod

pandas 2.0 did the same to methods, which shows up as the object form of the error: 'DataFrame' object has no attribute 'append' (use pd.concat([df, other])) and 'DataFrame' object has no attribute 'iteritems' (use .items()).

Fix the code for the version you want to run, then pin that version (numpy>=2,<3 in requirements.txt or pyproject.toml) so CI and production don’t drift. If code works locally but fails in a fresh environment, compare pip freeze from both; the versions differ.

A circular import

When two of your modules import each other, one of them runs while the other is only partly executed. Names defined below the import line don’t exist yet. Python says so directly:

Traceback (most recent call last):
  File "/app/main.py", line 1, in <module>
    import app.orders
  File "/app/app/orders.py", line 1, in <module>
    from app import billing
  File "/app/app/billing.py", line 4, in <module>
    TAX = orders.default_tax()
          ^^^^^^^^^^^^^^^^^^
AttributeError: partially initialized module 'app.orders' from '/app/app/orders.py' has no attribute 'default_tax' (most likely due to a circular import)

Read the frames top to bottom: main imports orders, whose first line imports billing, and billing uses orders at import time, while orders is still stuck on its line 1 and hasn’t defined default_tax yet. Each in <module> frame is one module in the middle of being imported, so the chain of those frames is the cycle. With from app.orders import default_tax instead of import app.orders, the same situation raises ImportError: cannot import name 'default_tax' from partially initialized module 'app.orders'.

On Python 3.13, if the module is a top-level file next to your script rather than inside a package, the message instead suggests renaming the file. If you know there’s no name collision, treat it as a circular import.

There are two fixes that hold up:

  • Move the shared code into a third module that both import, so neither imports the other.
  • If the dependency is only needed at call time, move the import into the function that uses it. This works, but it hides the cycle rather than removing it.

If the cycle exists only for type hints, put the import under if typing.TYPE_CHECKING: and quote the annotation.

Python imported a different module

A local file shadows the library

A file named random.py, json.py, requests.py, numpy.py, or test.py in your project (or a folder of that name) is found before the real module. It imports fine but has none of the library’s names. Python 3.13 detects this for the standard library:

AttributeError: module 'random' has no attribute 'randint' (consider renaming '/app/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)

On older versions you get only the first part. Check module.__file__: if it points into your project instead of the standard library or site-packages, rename your file and delete the stale __pycache__ folder next to it.

The wrong package is installed

Some import names are claimed by more than one PyPI package. The best-known case is module 'attr' has no attribute 's': the attrs library is imported as attr, and an unrelated package literally named attr installs a module with the same name. Fix it with pip uninstall attr followed by pip install --force-reinstall attrs. The same pattern appears with jwt (PyJWT versus the jwt package) and serial (pyserial versus serial). pip show <name> and module.__file__ reveal which one you have.

The program runs in another environment

If __version__ from your shell differs from what the failing program prints, they’re using different interpreters: a different virtualenv, the system Python under cron or systemd, or a Jupyter kernel that isn’t your project’s venv. Print sys.executable in the failing program and install the right version there.

The name is in a submodule

Importing a package doesn’t always import its submodules:

import xml
xml.etree.ElementTree     # AttributeError: module 'xml' has no attribute 'etree'

import xml.etree.ElementTree as ET   # works

logging.handlers is the same: import logging alone gives module 'logging' has no attribute 'handlers', so it needs its own import logging.handlers. A related mistake is looking in the parent module: os.join doesn’t exist, it’s os.path.join. Sometimes code works by accident because another module imported the submodule first, then breaks when import order changes.

Reading long import tracebacks

Import errors in larger apps (Django startup, pytest collection, Celery workers) produce tracebacks that pass through many framework frames before reaching the import that failed. The frames that matter are your own files, in order; they spell out the import chain. Debugly’s stack trace formatter highlights your own frames and folds the framework ones, which makes a circular import cycle easy to read off a pasted traceback.