Introduction

Python ImportError and ModuleNotFoundError exceptions occur when the Python interpreter cannot locate and load a module or package referenced in an import statement. ModuleNotFoundError (Python 3.6+) is a subclass of ImportError raised when a module cannot be found, while ImportError also covers circular import issues and failed imports due to code execution errors. Common causes include packages not installed in the current environment, virtual environment misconfiguration, sys.path not including the module location, working directory issues, __init__.py missing from packages, circular import dependencies, case-sensitive filesystem differences between development and production, Python version incompatibility, and compiled extension modules built for wrong architecture. The fix requires understanding Python's import system, package installation mechanics, virtual environment isolation, and path resolution. This guide provides production-proven troubleshooting for Python import errors across development, testing, and deployment environments.

Symptoms

  • ModuleNotFoundError: No module named 'requests'
  • ImportError: cannot import name 'X' from 'Y'
  • ImportError: attempted relative import with no known parent package
  • ImportError: circular import detected
  • Script works in development but fails in production
  • Works with python script.py but fails with python -m module
  • Different behavior between virtual environments
  • ImportError: No module named '__main__.X'
  • Package installed but still cannot import
  • Import works in REPL but fails in script
  • ImportError: DLL load failed (Windows)
  • ImportError: libXYZ.so: cannot open shared object file (Linux)

Common Causes

  • Package not installed in current Python environment
  • Virtual environment not activated
  • sys.path doesn't include project directory
  • Running script from wrong working directory
  • __init__.py missing from package directory
  • Circular import between modules
  • Relative import outside of package context
  • Package installed for different Python version
  • pip installed to different interpreter than running
  • Case mismatch in import path (works on Windows/macX, fails on Linux)
  • Compiled extension (.pyd, .so) built for wrong platform
  • Namespace package conflicts
  • .pyc cache files corrupted
  • PYTHONPATH environment variable misconfigured
  • Package renamed or restructured

Step-by-Step Fix

### 1. Diagnose the import error

Understand what Python is looking for:

```bash # Check which Python is running which python python --version python -c "import sys; print(sys.executable)"

# Check sys.path (where Python looks for modules) python -c "import sys; print('\n'.join(sys.path))"

# Typical output: # /home/user/project # /usr/lib/python39.zip # /usr/lib/python3.9 # /usr/lib/python3.9/lib-dynload # /home/user/.local/lib/python3.9/site-packages # /usr/local/lib/python3.9/dist-packages

# Check if package is installed python -c "import requests" # Will raise error if not found

# Or use pip to check pip list | grep requests pip show requests

# Output: # Name: requests # Version: 2.31.0 # Location: /home/user/.local/lib/python3.9/site-packages # Requires: charset-normalizer, idna, urllib3, certifi

# Check if pip matches python python -m pip --version # Should show same path as python --version

# If paths don't match, you have multiple Python installations ```

Verify package location:

```bash # Find where a package is installed python -c "import requests; print(requests.__file__)" # /home/user/.local/lib/python3.9/site-packages/requests/__init__.py

# For your own modules python -c "import mymodule; print(mymodule.__file__)"

# If module not found, check similar names python -c "import sys; print([p for p in sys.path if 'myproject' in p])"

# Check case sensitivity ls -la /path/to/project/ # Ensure import path matches actual directory name case ```

### 2. Fix virtual environment issues

Create and activate virtual environment:

```bash # Create virtual environment python -m venv venv

# Activate on Linux/macOS source venv/bin/activate

# Activate on Windows venv\Scripts\activate

# Verify activation which python # Should show venv/bin/python pip --version # Should show venv/lib/pythonX.X/site-packages

# Install dependencies pip install -r requirements.txt

# Verify installation pip list

# Deactivate when done deactivate ```

Common venv issues:

```bash # Issue: Script works outside venv but not inside # Cause: Dependencies installed globally but not in venv # Fix: Activate venv and reinstall source venv/bin/activate pip install -r requirements.txt

# Issue: Virtual environment created with wrong Python version # Check venv Python version venv/bin/python --version

# Fix: Recreate with correct version rm -rf venv python3.9 -m venv venv # Explicitly use Python 3.9 source venv/bin/activate pip install -r requirements.txt

# Issue: IDE using different interpreter than terminal # VS Code: Select interpreter with Ctrl+Shift+P > Python: Select Interpreter # PyCharm: Settings > Project > Python Interpreter # Ensure IDE interpreter matches your venv path

# Issue: Poetry virtualenv not activated # Poetry creates venvs in cache by default poetry env info # Show current environment poetry shell # Activate Poetry-managed venv # Or run commands with poetry run poetry run python script.py ```

Requirements.txt best practices:

```txt # requirements.txt # Pin exact versions for reproducibility requests==2.31.0 flask==2.3.3 sqlalchemy==2.0.20

# Or use pip-tools for locked dependencies # pip-tools generates requirements.txt from requirements.in # requirements.in: requests flask sqlalchemy

# Run: pip-compile requirements.in # Generates: requirements.txt with all transitive dependencies pinned

# Install with: pip install -r requirements.txt

# For production, also generate constraints pip freeze > requirements-prod.txt ```

### 3. Fix sys.path issues

Understanding sys.path:

```python # sys.path search order: # 1. Directory containing the input script (or current directory) # 2. PYTHONPATH environment variable directories # 3. Standard library directories # 4. .pth file paths # 5. site-packages directories

import sys print(f"Python executable: {sys.executable}") print(f"Python path:") for i, path in enumerate(sys.path): print(f" {i}: {path}")

# Add path temporarily (for current session) sys.path.insert(0, '/path/to/module') import mymodule # Now works

# Add path via PYTHONPATH environment variable export PYTHONPATH=/path/to/module:$PYTHONPATH python script.py

# Or in Python before import import os os.environ['PYTHONPATH'] = '/path/to/module' ```

Fix project structure imports:

``` # Project structure: myproject/ ├── src/ │ └── mypackage/ │ ├── __init__.py │ ├── module1.py │ └── module2.py ├── tests/ │ └── test_module1.py ├── requirements.txt └── setup.py

# Problem: Running tests from project root fails cd myproject python tests/test_module1.py # ImportError: cannot import 'module1' from 'mypackage'

# Fix 1: Install in development mode pip install -e . # Now mypackage is importable from anywhere

# Fix 2: Add src to PYTHONPATH export PYTHONPATH=src:$PYTHONPATH python tests/test_module1.py

# Fix 3: Run as module from src directory cd src python -m mypackage.module1

# Fix 4: Modify sys.path in test file import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) ```

Running as module vs script:

```bash # These are NOT equivalent:

# Running as script - __name__ == "__main__" python mypackage/module1.py # sys.path[0] = mypackage/ (directory of script)

# Running as module - __name__ == "mypackage.module1" python -m mypackage.module1 # sys.path[0] = current directory

# Relative imports only work when running as module # In module1.py: from .module2 import helper # Works with -m, fails with direct run

# Fix: Always run packages with -m python -m mypackage.module1

# Or use absolute imports from mypackage.module2 import helper # Works both ways (if mypackage in path) ```

### 4. Fix __init__.py issues

Package recognition:

```bash # Directory structure WITHOUT __init__.py: myproject/ └── mypackage/ ├── module1.py └── module2.py

# This will NOT work - mypackage is not recognized as a package python -c "from mypackage import module1" # ImportError: cannot import name 'module1' from 'mypackage'

# Fix: Add __init__.py (can be empty) myproject/ └── mypackage/ ├── __init__.py ├── module1.py └── module2.py

# Now works: python -c "from mypackage import module1" ```

__init__.py patterns:

```python # mypackage/__init__.py

# Option 1: Empty (just marks directory as package)

# Option 2: Export public API from .module1 import function1, Class1 from .module2 import function2, Class2

__all__ = ['function1', 'Class1', 'function2', 'Class2']

# Usage: from mypackage import function1 # Instead of from mypackage.module1 import function1

# Option 3: Package metadata __version__ = '1.0.0' __author__ = 'Author Name'

# Option 4: Lazy imports (avoid circular imports) def get_heavy_module(): from . import heavy_module return heavy_module

# Option 5: Conditional imports import sys if sys.version_info >= (3, 9): from ._modern import modern_func else: from ._legacy import legacy_func as modern_func ```

Namespace packages (no __init__.py):

```python # For namespace packages (PEP 420), __init__.py is optional # Useful for splitting package across directories

# pkg/ # namespace/ # package1/ # module.py # namespace/ # package2/ # module.py

# Both namespace directories can be in different locations # Python combines them at import time

# Usage in setup.py: from setuptools import setup, find_namespace_packages

setup( packages=find_namespace_packages(include=['pkg.*']), ) ```

### 5. Fix circular import issues

Identify circular imports:

```python # module_a.py from module_b import func_b

def func_a(): return func_b()

# module_b.py from module_a import func_a # CIRCULAR!

def func_b(): return func_a()

# This causes: ImportError: cannot import name 'func_a' from 'module_a'

# Python 3.7+ shows: ImportError: cannot import name 'func_a' from partially initialized module 'module_a' ```

Fix circular import patterns:

```python # Fix 1: Move import inside function (lazy import) # module_a.py def func_a(): from module_b import func_b # Import when needed return func_b()

# module_b.py def func_b(): from module_a import func_a return func_a()

# Fix 2: Use TYPE_CHECKING for type hints # module_a.py from typing import TYPE_CHECKING

if TYPE_CHECKING: from module_b import ClassB # Only for type checking

def func_a(obj: 'ClassB') -> None: pass

# module_b.py from typing import TYPE_CHECKING

if TYPE_CHECKING: from module_a import ClassA

def func_b(obj: 'ClassA') -> None: pass

# Fix 3: Create a third module for shared types # types.py class SharedType: pass

# module_a.py from types import SharedType from module_b import func_b

# module_b.py from types import SharedType from module_a import func_a

# Fix 4: Restructure to avoid circular dependency # Combine modules if they're tightly coupled # Or extract shared functionality to separate module ```

### 6. Fix relative import issues

Relative import syntax:

```python # Package structure: mypackage/ ├── __init__.py ├── module_a.py ├── subpkg/ │ ├── __init__.py │ ├── module_b.py │ └── subsubpkg/ │ ├── __init__.py │ └── module_c.py

# In module_a.py: from .subpkg.module_b import something # One level down from . import subpkg # Import subpackage

# In module_b.py: from ..module_a import something # One level up (parent) from ... import something # Two levels up (grandparent) from .subsubpkg.module_c import something # Down to subsubpkg

# In module_c.py: from ...module_a import something # Up two levels from ..module_b import something # Up one level then down ```

Relative import errors:

```python # Error: attempted relative import with no known parent package # Cause: Running module directly instead of as part of package

# Wrong: cd mypackage python module_a.py # Fails with relative imports

# Correct: cd .. python -m mypackage.module_a # Works

# Or use absolute imports instead: from mypackage.subpkg.module_b import something # Always works ```

### 7. Fix compiled extension issues

Native extension import errors:

```bash # Error: ImportError: DLL load failed (Windows) # or ImportError: libXYZ.so: cannot open shared object file (Linux)

# Check if extension module exists python -c "import numpy; print(numpy.__file__)"

# Check dependencies (Linux) ldd /path/to/module.cpython-39-x86_64-linux-gnu.so

# Check dependencies (macOS) otool -L /path/to/module.cpython-39-darwin.so

# Check dependencies (Windows) # Use Dependency Walker: https://dependencywalker.com/ ```

Fix compiled extensions:

```bash # Reinstall package with compiled extensions pip uninstall numpy pip install numpy --no-binary=:all: # Build from source

# Or use pre-built wheels pip install numpy --only-binary=:all:

# Check pip architecture pip debug --verbose | grep plat

# For macOS M1/M2 (ARM64): # Ensure using ARM64 Python, not x86_64 under Rosetta python -c "import platform; print(platform.machine())" # Should show: arm64 (not x86_64)

# If wrong architecture, reinstall Python: # brew install python@3.9 (for ARM64)

# For Windows with Visual C++ dependencies: # Install Microsoft C++ Build Tools # https://visualstudio.microsoft.com/visual-cpp-build-tools/

# Or use conda which includes compiled packages: conda install numpy ```

Virtual environment with system packages:

```bash # Create venv with access to system packages python -m venv --system-site-packages venv

# Or in virtualenv: virtualenv --system-site-packages venv

# This allows importing system-installed packages # Useful for large packages like numpy, scipy

# Warning: Reduces isolation, may cause version conflicts ```

### 8. Fix case sensitivity issues

Case mismatch detection:

```bash # Works on Windows/macOS (case-insensitive FS) but fails on Linux (case-sensitive)

# Check actual file names ls -la mypackage/ # Shows: Module1.py (capital M)

# But import uses: from mypackage import module1 # lowercase m

# Fix: Rename file to match import mv mypackage/Module1.py mypackage/module1.py

# Or change import to match file from mypackage import Module1

# Check all imports for case consistency find . -name "*.py" -exec grep -l "^import\|^from" {} \; | xargs grep -E "^(import|from)" ```

Git configuration for case sensitivity:

```bash # Git may not track case-only renames on case-insensitive filesystems git config core.ignorecase false

# Force Git to track case changes git mv mypackage/Module1.py mypackage/temp.py git mv mypackage/temp.py mypackage/module1.py git commit -m "Fix case of Module1.py" ```

### 9. Fix PYTHONPATH and environment issues

Environment variable configuration:

```bash # Check current PYTHONPATH echo $PYTHONPATH

# Add to PYTHONPATH (Linux/macOS) export PYTHONPATH=/path/to/project:$PYTHONPATH

# Add to PYTHONPATH (Windows) set PYTHONPATH=C:\path\to\project;%PYTHONPATH%

# Or in PowerShell $env:PYTHONPATH = "C:\path\to\project;$env:PYTHONPATH"

# Make permanent (Linux/macOS) echo 'export PYTHONPATH=/path/to/project:$PYTHONPATH' >> ~/.bashrc source ~/.bashrc

# Make permanent (Windows) # System Properties > Environment Variables ```

.pth file configuration:

```bash # Create .pth file in site-packages # This adds paths to sys.path automatically

echo "/path/to/project" > $(python -c "import site; print(site.getsitepackages()[0])")/myproject.pth

# Verify python -c "import sys; print([p for p in sys.path if 'project' in p])" ```

Docker Python path issues:

```dockerfile # Dockerfile with correct PYTHONPATH FROM python:3.9-slim

WORKDIR /app

# Install dependencies first (layer caching) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt

# Copy application COPY . .

# Set PYTHONPATH ENV PYTHONPATH=/app/src

# Run application CMD ["python", "-m", "mypackage.main"] ```

### 10. Debug import system

Import debugging tools:

```bash # Verbose import output python -v script.py 2>&1 | grep -E "import|ModuleNotFoundError"

# Shows every import attempt and result

# Use importlib to debug python -c " import importlib.util import sys

spec = importlib.util.find_spec('requests') print(f'Spec: {spec}') print(f'Origin: {spec.origin if spec else None}') print(f'Location: {spec.submodule_search_locations if spec else None}') "

# Check if module is built-in, frozen, or file-based python -c " import sys import importlib.machinery

module_name = 'sys' loaders = importlib.machinery.all_loaders() for loader_cls in loaders: loader = loader_cls.find_module(module_name) if loader: print(f'Found by: {loader_cls.__name__}') break else: print('Module not found') " ```

Import tracing in code:

```python # Enable import debugging import sys

# Track all imports imported_modules = [] original_import = __builtins__.__import__

def traced_import(name, *args, **kwargs): imported_modules.append(name) return original_import(name, *args, **kwargs)

__builtins__.__import__ = traced_import

# Run your code import mypackage

# Check what was imported print("Imported modules:", imported_modules)

# Restore original import __builtins__.__import__ = original_import

# Or use the importlib debug flag import importlib importlib.invalidate_caches() # Clear import caches before retry ```

Prevention

  • Use virtual environments for all projects (venv, poetry, conda)
  • Pin dependencies in requirements.txt with exact versions
  • Use pip install -e . for local development
  • Always use absolute imports for cross-package imports
  • Reserve relative imports for within-package imports only
  • Include __init__.py in all package directories
  • Test imports on case-sensitive filesystems (Linux) before deployment
  • Use python -m package.module instead of direct script execution
  • Configure IDE to use correct virtual environment interpreter
  • Document PYTHONPATH requirements in README
  • Use containerization (Docker) to ensure consistent environments
  • **AttributeError: module 'X' has no attribute 'Y'**: Module imported but missing expected attribute
  • **NameError: name 'X' is not defined**: Variable/function not in scope
  • **SyntaxError: invalid syntax**: Code cannot be parsed (often in imported file)
  • **IndentationError**: Python code indentation problem
  • **RecursionError: maximum recursion depth exceeded**: Often caused by circular imports