Common Python Module and Package Mistakes

Learn the most frequent mistakes developers make with Python modules and packages, from naming conflicts to import order problems, and how to avoid each one.

6 min read

When you are learning to structure Python code with modules and packages, certain common Python module and package mistakes appear so reliably that they are worth cataloging explicitly. These are not subtle edge cases that only affect advanced use cases. They are the kinds of errors that stop a beginner's program from running entirely, produce confusing tracebacks that do not point to the real problem, or create bugs that only surface when the code is run on a different machine or in a different order. Knowing the common Python module and package mistakes before you make them saves hours of debugging and builds good habits from the start.

Most of these mistakes stem from a few root causes: misunderstanding how Python's import search path works, underestimating the importance of the init file, creating mutual dependencies between modules, and placing code with side effects at module level where it runs at import time rather than at call time. Each of these root causes produces a family of related errors, and understanding the root cause lets you recognize and fix the entire family rather than treating each symptom individually.

Naming modules after standard library modules

This is the single most common mistake and the one with the most confusing symptoms. When you create a file with the same name as a module that ships with Python, every import statement in your project that tries to use the standard library version will find your file instead. The error messages do not say "you named your file wrong." They say the standard library function you are trying to call does not exist, because your file does not define it.

The list of names to avoid is long because Python ships with hundreds of standard library modules. Common accidental collisions include json.py, csv.py, email.py, typing.py, code.py, test.py, and types.py. Before naming a new module, run a quick import in a clean Python interpreter to check if the name is taken. If the import succeeds, pick a different name. Adding a project-specific prefix or suffix, like myapp_json.py or json_helpers.py, avoids the conflict while keeping the name descriptive.

This mistake is especially dangerous because it can work correctly on your machine and fail on another. If your json.py happens to provide a function with the same name and signature as the standard library function your code expects, the import succeeds silently and produces subtly wrong results instead of a clear error. The only reliable prevention is to never use standard library names for your own modules.

Forgetting or misplacing the init file

Every directory that should be treated as a Python package must contain an init.py file. Forgetting this file is easy to do when creating a new package directory, and the resulting ModuleNotFoundError does not explicitly say "you forgot the init file." It says the module cannot be found, which sends you looking for path configuration problems when the real issue is a missing marker file.

Subpackages also need their own init files. A package with three levels of nesting needs an init file at each level. The init files can be empty, and in small packages they often are, but they must exist. When moving modules between packages during a refactoring, it is easy to leave behind an init file or forget to create one in the new location, and the error only appears when something tries to import through that path.

The reverse mistake is also common: leaving an empty init file in a directory that is no longer a package after modules were moved out. An empty package directory with only an init file is technically valid Python but confusing to anyone navigating the codebase. Remove init files from directories that no longer contain modules, and remove empty package directories entirely unless they serve as namespace packages by design.

Creating side effects at module level

Any code that is not inside a function or class definition runs at import time, every time the module is first imported in a program session. A print statement that logs "Module loaded" seems helpful during development but produces noise in production logs. A database connection opened at module level connects to the database even when the importing code only needed a utility function that has nothing to do with databases.

Module-level side effects also create import-order dependencies that are invisible and fragile. If module A opens a configuration file at import time and module B imports module A, module B now has a hidden dependency on the configuration file existing at import time. If the configuration file is missing, module B fails to import even though module B itself does nothing with configuration. The error traceback points to module A, but the fix might be in whatever code creates the configuration file, and tracing that chain of dependencies is tedious.

The rule is simple: keep module-level code to definitions and constant assignments. If something needs to happen when the application starts, put it in an explicit initialization function and call that function from the entry point.

A clean module file should look like this, with only definitions at the top level and any script-only code guarded:

pythonpython
def process_data(items):
    return [item.strip().lower() for item in items if item]
 
if __name__ == "__main__":
    sample = ["  Apple ", "", "Banana  "]
    print(process_data(sample))

The guarded block runs only when the file is executed directly. When imported, only the function definition loads. This article on creating your first Python module covers the pattern of keeping module files clean and free of import-time side effects.

Relying on import order or specific search path entries

Code that works only when modules are imported in a particular sequence is fragile. A future change to an unrelated part of the codebase can change the import order and break code that was working fine. The same applies to code that depends on a specific directory being on sys.path through manual manipulation rather than through proper package installation. These dependencies are invisible in the source code and only surface when the code is run in a different context.

The fix is to structure your project so that imports work regardless of order and regardless of which script is the entry point. Installable packages that follow standard project layouts ensure that imports resolve consistently. The article on best practices for Python modules and packages covers the project structures that make import paths predictable.

A well-structured project keeps its importable code inside a named package directory and its entry-point scripts at the project root, creating a clear boundary between library and application:

pythonpython
from myproject.core import initialize
from myproject.cli import parse_args
 
def main():
    config = initialize()
    args = parse_args()
    return run(config, args)

Creating modules that are too large or too granular

A module with two thousand lines that mixes database queries, business logic, and HTML templating in a single file is too large. Anyone trying to understand or modify the code must hold too much context in their head at once. At the other extreme, a module with a single three-line function that calls another single-function module is too granular. The reader spends more time jumping between files than understanding the logic.

The sweet spot is a module that does one clearly named thing and is small enough that you can read the entire file in a few minutes. When a module exceeds about five hundred lines, look for natural subdivisions where one group of functions serves a different purpose than another group. When you have several modules with fewer than fifty lines each that are always imported together, consider whether they should be merged. The goal is cohesion: everything in the module changes for the same reason, and different reasons to change live in different modules.

Rune AI

Rune AI

Key Insights

  • Never name your modules after standard library modules; check the official module index if unsure.
  • Every package directory needs an init.py file, even if it is empty.
  • Avoid module-level side effects like print statements, file writes, and network calls.
  • Circular imports are a design problem, not an import-order problem; fix the dependency, not the statement order.
  • Keep a module focused on one purpose; split when it exceeds 500 lines or serves multiple unrelated concerns.
RunePowered by Rune AI

Frequently Asked Questions

Why does my module work on my machine but fail on a colleague's machine?

This usually happens because your module relies on being in the same directory as the importing script, and that directory is on sys.path on your machine but not on your colleague's. It can also happen if you depend on a third-party library that is installed on your machine but not in the project's requirements file. The fix is to structure your project as an installable package with a proper setup configuration so that the import paths are consistent across environments.

Why does changing a module file not update its behavior in a running program?

Python caches imported modules in sys.modules after the first import. Editing the .py file on disk does not affect the already-loaded module object in memory. During development, you need to restart the interpreter or use importlib.reload to pick up changes. In production, the standard practice is to restart the application process when code is deployed.

Is it bad practice to have many small modules instead of a few large ones?

Not necessarily. Small, focused modules are easier to understand, test, and maintain than large ones that mix unrelated concerns. The risk of too many small modules is fragmentation: if you need to open five files to understand a single feature, the modules might be too granular. A good rule of thumb is that a module should be small enough to describe its purpose in one sentence and large enough that splitting it further would create artificial separations between code that always changes together.

Conclusion

Most module and package mistakes fall into a few predictable categories: naming conflicts with the standard library, forgetting init files, creating circular imports, relying on specific import orders, and putting side effects at module level. Knowing these patterns lets you prevent the mistakes before they happen and diagnose them quickly when they do.