Back to Blog
Python

Python Module Attributes: Definition, Access, and Common Pitfalls

python module attributes: Learn how Python module attributes are defined, accessed, and modified, including built-in attributes like __all__ and practical pitfalls.

module attributesPython imports__all__module-level variablesattribute access
A visual representation of a Python module object with its attributes, showing a box with labeled keys and values.

In Python, a module is an object that holds attributes defined at the top level of a file. These python module attributes include variables, functions, and classes, and they are accessible from other modules after import. Understanding how these attributes behave is essential for writing clean, maintainable code, especially when you rely on shared state or control what a module exposes.

What Are Module Attributes?

When you define a name at the top level of a .py file, it becomes an attribute of the module object. Consider a simple module:

# mymodule.py x = 10 def foo(): return x

After importing mymodule, you can access x and foo as attributes:

import mymodule print(mymodule.x) # 10 print(mymodule.foo()) # 10

The module object itself is an instance of types.ModuleType, and its attributes are stored in a dictionary accessible via mymodule.__dict__. This dictionary maps attribute names to their values, and it is the same dictionary that Python uses for attribute lookup on the module.

Accessing and Modifying Module Attributes

The standard dot notation works for reading and writing module attributes. You can also use the built-in getattr and setattr functions when the attribute name is dynamic:

import mymodule # Read value = getattr(mymodule, 'x') # Write setattr(mymodule, 'x', 20) print(mymodule.x) # 20

Because module attributes are mutable, any code that imports the module sees the updated value. This is useful for configuration, but it also means that modifying a module attribute from one part of your program affects all other parts that import the same module. This shared-state behavior is a double-edged sword: it simplifies global configuration but can lead to subtle bugs if you are not careful.

Built-in Module Attributes

Every module has a set of predefined attributes that Python sets automatically. These attributes provide metadata about the module and its execution context. The most commonly used ones are:

AttributeDescription
__name__The module's name. When run as a script, it is "__main__".
__file__The path to the module's source file, if it was loaded from a file.
__doc__The module's docstring, or None if not defined.
__package__The package the module belongs to, or an empty string for top-level modules.
__spec__The module's import specification, used by the import system.
__all__A list of strings that controls wildcard imports.

For example, __name__ is often used to guard executable code:

if __name__ == "__main__": # run only when executed directly main()

__file__ is useful for locating resources relative to the module file, but be aware that it may be absent for modules loaded from other sources (e.g., from a database or a custom importer).

Controlling Wildcard Imports with all

The __all__ attribute is a list of strings that defines which names are imported when you use from module import *. Without __all__, a wildcard import brings in all names that do not start with an underscore. By setting __all__, you can explicitly control the public API of your module:

# mymodule.py __all__ = ['foo', 'x'] x = 10 y = 20 # not in __all__ def foo(): return x def _private(): return y

Now from mymodule import * imports only foo and x. This is a clean way to document what is intended to be public, and it also prevents accidental exposure of internal helpers. Note that __all__ only affects wildcard imports; you can still import y explicitly with import mymodule or from mymodule import y.

Module Attributes and the Import System

When you import a module, Python executes the module's code and creates a module object. The names defined at the top level become attributes of that object. The import statement then binds the module object to a name in the importing namespace. For example, import mymodule binds the name mymodule to the module object, while from mymodule import x binds the attribute x to a local name.

This distinction matters when you modify an attribute. If you do from mymodule import x and then assign x = 30 in your code, you are only rebinding the local name x; the module attribute mymodule.x remains unchanged. To modify the module attribute, you must use mymodule.x = 30 or setattr. This is a common source of confusion for developers new to Python's import semantics.

Mutable Module Attributes and Shared State

Because module attributes are shared across all importers, they act as a form of global state. This can be useful for caching, configuration, or simple counters, but it also introduces coupling. Consider a module that tracks a counter:

# counter.py count = 0 def increment(): global count count += 1 return count

Any code that imports counter and calls increment() will see the same count value. This is intentional, but it means that the module's behavior depends on the order and frequency of calls from different parts of the application. If you need isolated state, a class instance or a context variable may be a better choice.

Another pitfall is accidentally mutating a module attribute that is a mutable object, such as a list or dictionary. For example:

# config.py settings = {"debug": False}

If one module does config.settings["debug"] = True, the change is visible everywhere. This is often desirable for configuration, but it can make debugging difficult if the mutation happens unexpectedly. To reduce surprises, consider using immutable types or providing explicit setter functions.

Performance and Maintainability Considerations

Accessing a module attribute involves a dictionary lookup on the module's __dict__. This is slower than accessing a local variable, which is a simple array index. In performance-sensitive loops, you can bind a module attribute to a local variable to avoid repeated lookups:

import math # Slow in a loop for i in range(1000): y = math.sqrt(i) # Faster: bind to local sqrt = math.sqrt for i in range(1000): y = sqrt(i)

The difference is usually negligible for most applications, but it can matter in tight loops or when the attribute is accessed millions of times. More importantly, using module attributes as global configuration can make code harder to test because the state persists across test cases. To improve testability, consider passing configuration as arguments or using dependency injection, reserving module attributes for truly global constants that do not change.

When you design a module, think about which attributes are part of its public API. Use __all__ to signal that, and avoid exposing internal implementation details. This makes the module easier to maintain and reduces the risk of accidental breakage when you refactor internal names.

python module attributes: Practical Usage and Code Examples | RYUSLOG DEV