vault backup: 2026-05-30 16:02:33

This commit is contained in:
Rainyy21
2026-05-30 16:02:33 -04:00
commit 96449f8968
43 changed files with 2837 additions and 0 deletions
@@ -0,0 +1,152 @@
---
title: What is __init__.py?
created: 2026-05-22
tags:
- python
- python_note
- packaging
- reference
category: python_note
status: reference
up: "[[project/index]]"
related:
- "[[poetry_guide]]"
- "[[poetry_project_ideas]]"
source:
author:
published:
---
# What is `__init__.py`?
`__init__.py` is the file that tells Python "this folder is a package". When you put it inside a directory, that directory becomes importable like a module.
## The Core Purpose
Without `__init__.py` (in older Python, ≤3.2), a folder was just a folder — Python could not `import` from it. With it, the folder becomes a **package** you can do this with:
```python
from recipe_scraper.scraper import fetch_recipe
```
Here, `recipe_scraper/` is a package because it contains `__init__.py`.
> Note: Since Python 3.3, "namespace packages" allow imports without `__init__.py`, but **regular packages still use it** because it gives you more control (init code, explicit exports, IDE/tooling support).
---
## What It Does in Practice
### 1. Marks the folder as a package
Even an **empty** `__init__.py` is meaningful. It signals to Python:
> "Treat this directory as something you can import from."
### 2. Runs initialization code
Anything inside `__init__.py` runs **the first time** the package is imported. This is useful for:
- Setting up logging
- Loading config
- Registering plugins
```python
# recipe_scraper/__init__.py
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())
```
### 3. Controls the package's public API
You can re-export things so users don't need to know the internal file layout:
```python
# recipe_scraper/__init__.py
from .scraper import fetch_recipe
from .parser import parse_recipe
from .db import save_recipe
__all__ = ["fetch_recipe", "parse_recipe", "save_recipe"]
```
Now consumers can write the short form:
```python
from recipe_scraper import fetch_recipe # clean
# instead of:
from recipe_scraper.scraper import fetch_recipe # verbose
```
### 4. Defines package metadata
A common pattern is exposing a version string:
```python
# recipe_scraper/__init__.py
__version__ = "0.1.0"
```
---
## How It Fits the Recipe Scraper Project
Given the folder structure from [[Recipe Web Scraper - Getting Started]]:
```
recipe-scraper/
├── pyproject.toml
└── recipe_scraper/
├── __init__.py ← makes this a package
├── scraper.py
├── parser.py
├── db.py
└── cli.py
```
The `__init__.py` here lets you:
1. Run `poetry run python -m recipe_scraper.cli` — only works because `recipe_scraper` is a package.
2. Import cleanly from anywhere in the project:
```python
from recipe_scraper.db import Recipe
```
3. Optionally expose a tidy top-level API:
```python
# recipe_scraper/__init__.py
from .scraper import scrape_url
from .db import Recipe, init_db
__version__ = "0.1.0"
__all__ = ["scrape_url", "Recipe", "init_db"]
```
So users of your library can just do:
```python
import recipe_scraper
recipe_scraper.scrape_url("https://...")
```
---
## When to Leave It Empty vs. Fill It
| Situation | What to put in `__init__.py` |
| :--- | :--- |
| Internal-only package, no public API | Empty file |
| You want a clean import surface | Re-exports + `__all__` |
| Library shipped to PyPI | `__version__`, re-exports, maybe logging setup |
| One-time setup needed (config, env) | Initialization code at the top |
**Rule of thumb:** start with an empty `__init__.py`. Only add code when you have a concrete reason — re-exports, version, or setup. Don't put heavy logic in `__init__.py`; it runs on every import.
---
## Common Gotchas
- **Heavy imports slow everything down.** If `__init__.py` imports a big library (e.g., `pandas`), every `import recipe_scraper.anything` pays that cost. Keep it light.
- **Circular imports** often start in `__init__.py`. If `__init__.py` imports from `scraper.py`, and `scraper.py` imports from the package root, you get a cycle. Use lazy imports or restructure.
- **Tests need it too.** A `tests/` folder usually has an empty `__init__.py` so pytest can discover test modules consistently (though pytest's `rootdir` config can avoid this).
---
## TL;DR
- `__init__.py` = "this folder is a Python package".
- Can be empty — just its presence matters.
- Use it to **re-export** the public API, set `__version__`, or run small setup.
- Keep it light: every import of the package runs it.
## Related
- [[Recipe Web Scraper - Getting Started]]
- [[poetry_guide]] — Poetry's `poetry new` creates this file automatically