When you start building packages with multiple subpackages and nested modules, using absolute and relative imports in Python correctly becomes essential. Python provides two distinct systems for this: absolute imports, which spell out the full dotted path from the project root, and relative imports, which use leading dots to navigate up and down the package hierarchy. Understanding when and how to use each system is essential for building packages that work correctly whether they are run as scripts, imported by other packages, or installed as third-party libraries.
The choice between absolute and relative imports in Python is not purely cosmetic. Using the wrong style in the wrong context causes ImportError messages that can be confusing if you do not understand how Python resolves import paths. The most common of these, "attempted relative import beyond top-level package," appears when you run a file that uses relative imports directly as a script instead of using the module flag. This article explains what absolute and relative imports are, how they differ, and which style to choose for each situation.
What absolute imports are
An absolute import specifies the complete dotted path to a module starting from the project root or from a top-level package that is on Python's search path. If your project has a package called data with a subpackage called io that contains a module called readers, an absolute import from anywhere in the project looks the same: you write the full path from the root, and Python resolves it regardless of which file contains the import statement.
Absolute imports are the default style in Python 3 and are recommended for most situations. They are explicit: anyone reading the import statement can trace the exact location of the imported module without needing to know where the importing file lives in the directory tree. They are also context-independent: moving the importing file to a different directory does not break the import as long as the target module's location relative to the project root has not changed.
The following example shows a small package structure and the corresponding absolute imports. Suppose your project has a utils directory containing a helpers module and a main script at the project root. The main script imports helpers with the full dotted path from the root, and the same import statement would work from any file in the project:
from utils import helpers
result = helpers.clean_text(user_input)This style works because the project root is on Python's search path, so the utils package is discoverable from any location. When you later install your project as a distributable package, absolute imports continue to work without modification because the package name becomes part of the installed namespace and the search path handles the resolution automatically.
Compare this with a relative import that navigates from a module inside the package to a sibling module. The relative style uses dots instead of the package name, which keeps the import short even when the package is deeply nested:
from . import helpers
result = helpers.clean_text(user_input)The single dot means "the current package," and Python resolves it relative to the file that contains the import statement. This style is covered in depth alongside all other import variants in the article on different ways to import Python modules.
What relative imports are
A relative import uses leading dots to specify a module's location relative to the file that contains the import statement. A single dot means the current package, two dots mean the parent package, three dots mean the grandparent, and so on. Relative imports are only valid inside packages; you cannot use them in a top-level script that is run directly because there is no package context for the dots to resolve against.
Relative imports are most useful in large packages with deeply nested subpackages, where writing the full absolute path on every import would mean repeating a long chain of package names dozens of times. A module buried three levels deep can import a sibling module in the same subpackage with a single dot, and it can import a module from a parallel subpackage with two dots. This conciseness makes the imports shorter and makes it explicit that the imported module is part of the same package family rather than an external dependency.
The import statement for a sibling module in the same package uses a single dot before the module name. For a module in the parent package, two dots navigate up one level before specifying the target. The dots replace the need to know or repeat the absolute package name, which is especially valuable when the package is renamed or restructured and the absolute paths would all need to be updated.
When relative imports fail
The most common error with relative imports occurs when you run a file inside a package directly as a script. If your package has a module at the path mypackage/subpackage/module.py and that module contains a relative import, running the file with python mypackage/subpackage/module.py will fail because Python does not know that the file is part of a package. The interpreter sees the file as a standalone script with no package context, the leading dots have no hierarchy to resolve against, and the result is the "attempted relative import beyond top-level package" error.
The fix is to run the module using the module flag from the project root directory. Instead of running the file directly, you use python -m mypackage.subpackage.module. This tells Python to treat the module as part of a package, which establishes the package hierarchy that relative imports need. The same module flag is how you run standard library modules like http.server and venv, and it is the standard way to execute package code that relies on relative imports.
Another common failure happens when you use too many dots and try to navigate above the top-level package. If your project structure has a top-level package called app and you use three dots from a module that is only two levels deep, Python cannot resolve the import because there is no grandparent package to reach. The solution is to either reduce the number of dots to match the actual package depth or to switch to an absolute import for modules that live above the current package boundary.
Choosing the right style for your project
Absolute imports should be your default choice. They are explicit, they work from scripts and from imported modules alike, and they are the style that most Python developers expect to see when reading unfamiliar code. Use absolute imports for all imports in your main entry-point script, for importing modules from other top-level packages, and for importing standard library and third-party modules. Absolute imports also survive refactoring better because tools like IDEs and static analyzers can trace them reliably.
Relative imports become a reasonable choice when your package has three or more levels of nesting and the absolute paths would be longer than the content they are importing. A module in a deep subpackage that needs to import five different sibling modules can use a single dot for each import instead of repeating the full absolute path five times. This pattern is common in large web frameworks and data processing libraries where the package structure mirrors the conceptual architecture and the absolute paths are genuinely unwieldy.
You can also use a hybrid approach that many large codebases adopt: use absolute imports for external dependencies and for imports that cross major subsystem boundaries, and use relative imports for imports between closely related modules within the same subsystem. The key is consistency within a file: do not import the same module with an absolute path on line 5 and a relative path on line 20. For more guidance on keeping imports organized as your project scales, the article on organizing Python code with packages covers project structure patterns that make import paths predictable and maintainable.
Rune AI
Key Insights
- Absolute imports specify the full dotted path from the project root and work consistently from any file.
- Relative imports use leading dots to navigate the package hierarchy and are only valid inside packages.
- Running a file that uses relative imports with python filename.py fails; use python -m package.module instead.
- Use absolute imports by default and switch to relative imports only when package depth makes absolute paths unwieldy.
- A single leading dot means the current package, two dots means the parent, and three dots means the grandparent.
Frequently Asked Questions
When should I use absolute imports instead of relative imports?
Why do relative imports fail with 'attempted relative import beyond top-level package'?
Can I mix absolute and relative imports in the same file?
Conclusion
Absolute imports are the safe default: they are explicit, work from any context, and are easy to trace. Relative imports are a specialized tool for large packages where deeply nested modules need to reference siblings without repeating long absolute paths. The most common relative import error happens when running a package file directly as a script; using the python -m flag from the project root fixes it.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.