| jupytext |
|
||||||||
|---|---|---|---|---|---|---|---|---|---|
| kernelspec |
|
In Python, all data types have a set of special attributes, commonly referred to as dunder attributes1.
These attributes provide developers with useful metadata about Python objects. They follow the naming pattern __attribute__ — with two leading and two trailing underscores where attribute is the name of the given dunder attribute.
Python’s built-in objects come with many such attributes. Some external libraries also define their own custom dunder attributes, but this practice is generally discouraged unless it is truly necessary and thoroughly documented {cite}python:lexical_identifiers.
In this section, we will explore the most important core dunder attributes across different types of Python objects.
The __name__ attribute holds the name of a module.
:class: hint
Not sure what a Python module is? According to the official Python tutorial, a module is simply
“[...] **a file** containing Python definitions and statements” {cite}`python:tutorial_modules`.
By default, the module name corresponds to the filename without the .py suffix {cite}python:tutorial_modules.
For example, consider the file my_math.py
# file my_math.py
def multiply(a: int, b: int) -> int:
return a * bNow, if we import this module:
import my_math
print(my_math.__name__)The output will be:
my_mathHowever, there is a special case: the __mame__ attribute takes the value: __main__ when a module is executed directly {cite}python:datamodel_specialnames.
This covers a few cases {cite}python-ref-import-mainspec:
print( __name__)
Let's modify my_math.py to display the value __main__.
# file my_math.py
print(__name__)
def multiply(a: int, b: int) -> int:
return a * bIf we now run:
python3 my_math.pyThe output will be:
__main__
The same happens when you execute code passed as a string:
python -c "print(__name__)"Output:
__main__
echo "print(__name__)" | pythonOutput:
__main__
:class: hint
Because `__name__` is set to `__main__` only when a file is executed directly, you will often see Python code guarded like this:
```python
if __name__ == "__main__":
...
```
This pattern allows developers to include logic that should only run on direct execution, while preventing it from running when the file is imported as a module.
Let’s investigate with an example:
```{code-cell} python3
# file: my_math.py
def multiply(a: int, b: int) -> int:
return a * b
if __name__ == "__main__":
print("I'm executed directly!")
```
Now compare the two cases:
- running `python my_math.py` will print `I'm executed directly!`
- importing it inside a Python shell (or any module) with `import my_math` will print nothing
The __spec__ attribute is an instance of ModuleSpec.
It contains the specification for the module and is central to the import system.
Some of its most relevant attributes are listed below ({numref}module-spec):
:name: module-spec
* - **Attribute**
- **Meaning**
* - `name`
- The module's fully qualified name.
* - `origin`
- Path to the file (`.py`) where the module is defined.
May be `None` (e.g., for namespace packages; see {cite}`pep-0420`.
* - `loader`
- The [`Loader`](https://docs.python.org/3/library/importlib.html#importlib.abc.Loader) implementation
* - `cached`
- The path of the compiled module's code (can be `None`)
:class: attention
For code executed directly (see [`__name__`](#__name__)), `__spec__` is usually `None` {ref}`python-ref-import-mainspec`.
There are two main exceptions:
1. **When running as a module using the `-m` option**:
```bash
python -m my_module
```
2. **When executing a directory or a zip file containing a `__main__.py` file**:
Example project structure:
```text
my_project/
├── __main__.py
└── utils.py
```
Contents of `__main__.py`:
```python
import sys
print(f"Running {__name__=}")
print(f"{__spec__=}")
```
Running the directory as a script:
```bash
python my_project
```
Output (simplified):
```
Running __name__='__main__'
__spec__=ModuleSpec(name='__main__', loader=..., origin='my_project/__main__.py')
```
:class: warning
If you are already experienced in Python, you probably know that literally **all data** in the Python ecosystem are **objects** {cite}`python:datamodel`. This means that modules, functions, methods, and classes **are themselves objects**, not just instances of classes (that, by the way, are objects too).
In this section, the "Of Objects" subsection covers the **core special attributes** that are fundamental to **all** Python objects. The previous subsections (Modules, Functions, Methods, Classes) describe **additional specialized attributes** specific to those object types. Therefore, objects of those types will have both the general object attributes listed here and their type-specific attributes described above.
Footnotes
-
The term dunder comes from double underscore. ↩