# Packages, ‏__init__.py ו-if __name__ == "__main__"<no value>

## מה נלמד?

- **Package** (חבילה) — תיקייה שמכילה מודולים.
- הקובץ **`__init__.py`** ותפקידו.
- ייבוא מתוך Package.
- המשתנה המיוחד **`__name__`**, והתבנית **`if __name__ == "__main__":`**.

## למה צריך את זה?

כשיש 3 מודולים, הם יכולים לשבת באותה תיקייה. כשיש 30, צריך **תיקיות**: כל מה שקשור למשתמשים בתיקייה אחת, כל מה שקשור לתשלומים באחרת. כך בנויים כל הפרויקטים האמיתיים, וגם כל הספריות שתתקינו (FastAPI הוא Package ענק עם עשרות תת-Packages).

ו-`if __name__ == "__main__":` פותר את הבעיה מהשיעור הקודם: קוד בדיקה שרץ בכל פעם שמישהו מייבא את הקובץ.

## הסבר מהבסיס

### Package = תיקייה של מודולים

**Package** הוא תיקייה שמכילה מודולים (קבצי `.py`), ובדרך כלל גם קובץ בשם **`__init__.py`**.

```text
store/                    ← project folder
├── main.py               ← the program you run
└── inventory/            ← a PACKAGE
    ├── __init__.py       ← marks this folder as a package
    ├── products.py       ← module
    └── stock.py          ← module
```

### `__init__.py`

- **מסמן** שהתיקייה היא Package, באופן מפורש וברור.
- **רץ אוטומטית** בפעם הראשונה שמייבאים משהו מה-Package.
- בדרך כלל **ריק**. אפשר לשים בו ייבוא נוח, כדי שאפשר יהיה לכתוב `from inventory import add_product` במקום `from inventory.products import add_product`.

(ב-Python המודרנית אפשר לייבא גם מתיקייה בלי `__init__.py`, אבל המוסכמה המקובלת היא לשים אותו, כדי שיהיה ברור שזו חבילה ולא סתם תיקייה.)

### ייבוא מ-Package

הנקודה מפרידה בין **תיקייה** למודול, בדיוק כמו `/` בנתיב:

```python
import inventory.products                 # inventory/products.py
from inventory import products            # same module, shorter use
from inventory.products import add_product   # one function
```

### `__name__` — "מי אני?"

לכל מודול יש משתנה מובנה בשם **`__name__`**. Python קובעת את ערכו לפי **איך הקובץ הופעל**:

| איך הקובץ הופעל | הערך של `__name__` |
|---|---|
| **הורץ ישירות**: `python prices.py` | `"__main__"` |
| **יובא** מקובץ אחר: `import prices` | `"prices"` (שם המודול) |

ולכן:

```python
if __name__ == "__main__":
    # runs ONLY when this file is run directly, NOT when it is imported
```

{{< box "analogy" "אנלוגיה: שף שהוא גם ספק" >}}
שף מבשל במסעדה שלו (הרצה ישירה), ובמקביל מוכר רטבים לחנויות (ייבוא). כשהוא במסעדה, הוא גם מגיש ללקוחות: זה הקוד שבתוך `if __name__ == "__main__"`. כשחנות קונה רוטב, היא מקבלת **רק את הרוטב** (הפונקציות), ולא ארוחה שלמה שמוגשת אצלה בכוח.
{{< /box >}}

## תחביר

```text
project/
├── main.py
└── package_name/
    ├── __init__.py
    └── module_name.py
```

```python
# main.py
from package_name.module_name import some_function
from package_name import module_name


# any module that can also be run directly:
def main():
    ...          # what happens when the file is run as a program


if __name__ == "__main__":
    main()
```

התבנית של פונקציה `main()` + התנאי בסוף הקובץ היא **הסטנדרט** בכל קובץ Python שמיועד להרצה.

## דוגמת קוד ראשונה

נראה את `__name__` בפעולה. `week-2/name_demo/greetings.py`:

```python
def hello(name):
    return "Hello, " + name + "!"


print("greetings.py: __name__ is", __name__)

if __name__ == "__main__":
    print("Running greetings.py directly - quick self test:")
    print(hello("Tester"))
```

`week-2/name_demo/app.py`:

```python
import greetings

print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))
```

**הרצה 1:** `python greetings.py`

```text
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!
```

**הרצה 2:** `python app.py`

```text
greetings.py: __name__ is greetings
app.py: __name__ is __main__
Hello, Dana!
```

## פירוק הקוד שורה אחר שורה

| | הרצה 1 (`python greetings.py`) | הרצה 2 (`python app.py`) |
|---|---|---|
| `__name__` של `greetings.py` | `"__main__"` (הוא הקובץ שהורץ) | `"greetings"` (הוא יובא) |
| ה-`print` הראשון ב-greetings | רץ | **רץ** (קוד ברמה הראשית תמיד רץ בייבוא) |
| הבלוק `if __name__ == "__main__"` | **רץ** (בדיקה עצמית) | **לא רץ** |
| `__name__` של `app.py` | — | `"__main__"` |

**המסקנה:** כל מה שצריך לרוץ **רק כשמריצים את הקובץ ישירות** (בדיקות, הדגמות, הפעלת התוכנית) נכנס לתוך התנאי. הגדרות של פונקציות וקבועים נשארות בחוץ, כדי שאפשר יהיה לייבא אותן.

## דוגמה נוספת

**פרויקט עם Package אמיתי.** מבנה:

```text
week-2/store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`inventory/__init__.py`:

```python
from inventory.products import create_product
from inventory.stock import add_stock, remove_stock
```

`inventory/products.py`:

```python
def create_product(name, price):
    if price < 0:
        raise ValueError("price cannot be negative, got " + str(price))
    return {"name": name, "price": price, "quantity": 0}


if __name__ == "__main__":
    print(create_product("Test item", 10))
```

`inventory/stock.py`:

```python
def add_stock(product, amount):
    product["quantity"] = product["quantity"] + amount


def remove_stock(product, amount):
    if amount > product["quantity"]:
        raise ValueError("not enough " + product["name"] + " in stock")
    product["quantity"] = product["quantity"] - amount
```

`main.py`:

```python
from inventory import create_product, add_stock, remove_stock


def main():
    laptop = create_product("Laptop", 3500)
    add_stock(laptop, 10)
    remove_stock(laptop, 3)
    print(laptop)

    try:
        remove_stock(laptop, 50)
    except ValueError as error:
        print("Error:", error)


if __name__ == "__main__":
    main()
```

**הרצה** (מתוך `week-2/store`): `python main.py`

```text
{'name': 'Laptop', 'price': 3500, 'quantity': 7}
Error: not enough Laptop in stock
```

**פירוק:**

- `__init__.py` **מרכז** את הפונקציות החשובות של ה-Package. בזכותו `main.py` כותב `from inventory import create_product`, ולא צריך לדעת באיזה קובץ פנימי בדיוק היא יושבת. ה-Package נראה מבחוץ כמו "יחידה אחת".
- `add_stock(laptop, 10)` **משנה את ה-dict עצמו**, כי dict הוא Mutable (נושא מבני נתונים ופונקציות!). הפונקציה לא צריכה להחזיר כלום.
- `products.py` כולל בדיקה עצמית בתוך `if __name__ == "__main__"`. אפשר להריץ אותו לבד כדי לבדוק אותו, והוא לא "מלכלך" כשמייבאים אותו.
- `main.py` עטוף ב-`main()` + התנאי. אם מישהו ייבא אותו בעתיד, שום דבר לא ירוץ מעצמו.

{{< box "tip" "איך מריצים קובץ שבתוך Package?" >}}
אם מנסים `python inventory/products.py`, מקבלים לעיתים `ModuleNotFoundError`, כי Python מחפשת מודולים מתוך התיקייה **של הקובץ שהורץ** (`inventory/`), ולא מתיקיית הפרויקט. הדרך הנכונה להריץ מודול שבתוך Package היא מתיקיית הפרויקט, עם הדגל `-m` (module) ו**נקודות במקום סלאשים**: `python -m inventory.products`.
{{< /box >}}

## טעות נפוצה

**הטעות 1: לכתוב `"main"` במקום `"__main__"`.**

```python
if __name__ == "main":
    main()
```

שום דבר לא רץ, ואין שגיאה! הערך הוא `"__main__"`, עם **שני קווים תחתונים מכל צד**. Logical Error שקט.

**הטעות 2: ייבוא מעגלי (Circular Import).**

`a.py` מייבא את `b`, ו-`b.py` מייבא את `a`:

```text
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)
```

שני מודולים שתלויים זה בזה הם סימן לחלוקה לא טובה. מוציאים את החלק המשותף למודול שלישי, ששניהם מייבאים ממנו.

**הטעות 3: לשים את כל הלוגיקה בתוך בלוק ה-`if`.**

```python
if __name__ == "__main__":
    def calculate(x):
        return x * 2
    print(calculate(5))
```

עכשיו אי אפשר לייבא את `calculate` מקובץ אחר, כי היא מוגדרת רק בהרצה ישירה. הפונקציות **בחוץ**, ורק **ההפעלה** בפנים.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.

### תרגיל 1: מבנה של Package

ציירו את מבנה התיקיות של פרויקט `store` עם קובץ `main.py` ו-Package בשם `inventory`, שבו שני מודולים: `products.py` ו-`stock.py`. איזה קובץ חייב להיות בתוך `inventory`, ולמה?

{{< solution >}}
```text
store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`__init__.py` מסמן את התיקייה כ-**Package** — תיקייה של מודולים שאפשר לייבא ממנה. הוא יכול להיות ריק.
{{< /solution >}}

### תרגיל 2: ייבוא מ-Package

ב-`inventory/products.py` יש פונקציה `create_product(name, price)` שמחזירה מילון. כתבו ב-`main.py` שלוש דרכים לייבא ולהשתמש בה.

{{< solution >}}
```py
import inventory.products
laptop = inventory.products.create_product("Laptop", 3500)

from inventory import products
mouse = products.create_product("Mouse", 80)

from inventory.products import create_product
desk = create_product("Desk", 900)
```

הנקודה בנתיב (`inventory.products`) היא כמו `/` בתיקיות: המודול `products` בתוך ה-Package `inventory`.
{{< /solution >}}

### תרגיל 3: מה זה __name__?

בקובץ `greetings.py`:

```text
def hello(name):
    return "Hello, " + name + "!"

print("__name__ is", __name__)
```

מה יודפס בכל מקרה?

1. מריצים `python greetings.py`.
2. מריצים `main.py`, שבו כתוב `import greetings`.

{{< solution >}}
1. בהרצה ישירה: `__name__ is __main__`.
2. בייבוא: `__name__ is greetings` — השם של המודול.

Python מגדירה את `__name__` לפי **איך** הקובץ הופעל. זה הבסיס ל-main guard.
{{< /solution >}}

### תרגיל 4: Main guard

הוסיפו ל-`greetings.py` (מהתרגיל הקודם, עם הפונקציה `hello`) בדיקה עצמית שרצה **רק** כשמריצים את הקובץ ישירות, ולא כשמייבאים אותו. הבדיקה מדפיסה `quick self test:` ואז את התוצאה של `hello("Tester")`.

את השורה `print("__name__ is", __name__)` מהתרגיל הקודם מוחקים.

**פלט צפוי (python greetings.py):**

```text
quick self test:
Hello, Tester!
```

{{< solution >}}
```python
def hello(name):
    return "Hello, " + name + "!"


if __name__ == "__main__":
    print("quick self test:")
    print(hello("Tester"))
```

```text
quick self test:
Hello, Tester!
```

כשקובץ אחר עושה `import greetings`, התנאי `False` — הבדיקה לא רצה, ורק הפונקציה זמינה.
{{< /solution >}}

### תרגיל 5: חבילת mathtools

בנו Package בשם `mathtools` עם שני מודולים: `basic.py` עם `add(a, b)`, ו-`stats.py` עם `mean(numbers)` ו-`maximum(numbers)`. ב-`main.py` השתמשו בשלושתן.

```text
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])
```

**פלט צפוי:**

```text
add: 5
mean: 20.0
maximum: 30
```

{{< solution >}}
```text
math_project/
├── main.py
└── mathtools/
    ├── __init__.py
    ├── basic.py
    └── stats.py
```

**mathtools/basic.py**

```py
def add(a, b):
    return a + b
```

**mathtools/stats.py**

```py
def mean(numbers):
    return sum(numbers) / len(numbers)


def maximum(numbers):
    return max(numbers)
```

**main.py**

```py
from mathtools.basic import add
from mathtools import stats

print("add:", add(2, 3))
print("mean:", stats.mean([10, 20, 30]))
print("maximum:", stats.maximum([10, 20, 30]))
```
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **Package** = תיקייה של מודולים, עם **`__init__.py`** שרץ בייבוא הראשון ויכול "לחשוף" שמות נוחים.
- ייבוא מ-Package: `from package.module import name`. הנקודה = מעבר לתוך תיקייה.
- **`__name__`** הוא `"__main__"` כשהקובץ הורץ ישירות, ושם המודול כשהוא יובא.
- **`if __name__ == "__main__": main()`** — הפעלה רק בהרצה ישירה. פונקציות מגדירים בחוץ.
- מודול בתוך Package מריצים מתיקיית הפרויקט עם `python -m package.module`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## דוקומנטציה רשמית

- [Python Tutorial — Packages](https://docs.python.org/3/tutorial/modules.html#packages) — תיקייה עם __init__.py
- [__main__ — Top-level code environment](https://docs.python.org/3/library/__main__.html#name-main) — if __name__ == "__main__"

<!-- docs:end -->
