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

AttributeError: module 'X' has no attribute 'Y'

1 · The lesson

read

What this error means

You imported a module successfully, but the function or class you tried to use isn't inside it. Python found the module file, just not the name you asked for. This is different from ModuleNotFoundError: here the module loaded fine, and only the name inside it is missing.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    os.fakeattr()
AttributeError: module 'os' has no attribute 'fakeattr'

Why it happens

There are five common causes:

1. Typo or wrong case. os.getEnv instead of os.getenv.
2. Version mismatch. The function exists in a newer (or older) version of the library. np.float was deprecated in NumPy 1.20 and removed in 1.24. asyncio.coroutine was removed in Python 3.11.
3. Wrong module. You confused two libraries with similar names: pickle vs cPickle, urllib vs urllib2 vs urllib3, Image (PIL) vs image (matplotlib).
4. Circular import. Module a imports b, and b imports a again. When the second import runs, a is only half-loaded, and the name you want hasn't been defined yet.
5. Local file shadowing the real library. You have a file called random.py in your project, so import random; random.choice(...) looks inside your file, not in the standard library.

How to fix it

Check the spelling against the official docs, and use dir(module) to see which names the module really has:

python
import os
print([name for name in dir(os) if name.startswith("get")])

Pin or upgrade the library version if the function was added/removed:

bash
pip install "numpy>=1.20"
pip show numpy           # check installed version

Rename a local file that hides a standard library module:

python
mv random.py my_random.py
rm random.pyc __pycache__/random.*

Break the circular import by moving the shared import inside a function, or by restructuring so neither module depends on the other at import time:

python
def use_b():
    from b import helper    # import lazily, inside the function
    helper()

Use getattr with a default when you really don't know whether the attribute exists in every version you support:

python
fn = getattr(some_module, "new_api", some_module.old_api)
+ setup added so this can run · defines some_module
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

some_module = _AutoMock('some_module')

When you'd see this in real code

  • A tutorial uses the removed np.float alias. Use float or np.float64 instead.
  • A json.parse(...) call carried over from JavaScript, when Python's function is json.loads.
  • A file named email.py in your project breaks import email from the standard library.
  • After upgrading Django from 3.x to 4.x, django.utils.translation.ugettext is gone (renamed to gettext).
  • A circular import between models.py and views.py in a Flask/Django app.

See Also

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