--- 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