Packages, __init__.py ו-if __name__ == "__main__"
- להסביר מהו Package ולבנות אחד עם
__init__.py - לייבא מודולים ופונקציות מתוך Package
- להסביר מה הערך של
__name__כשמריצים קובץ לעומת כשמייבאים אותו - להשתמש ב-
if __name__ == "__main__":כדי להפריד בין “ספרייה” לבין “תוכנית”
מה נלמד?
- Package (חבילה) — תיקייה שמכילה מודולים.
- הקובץ
__init__.pyותפקידו. - ייבוא מתוך Package.
- המשתנה המיוחד
__name__, והתבניתif __name__ == "__main__":.
למה צריך את זה?
כשיש 3 מודולים, הם יכולים לשבת באותה תיקייה. כשיש 30, צריך תיקיות: כל מה שקשור למשתמשים בתיקייה אחת, כל מה שקשור לתשלומים באחרת. כך בנויים כל הפרויקטים האמיתיים, וגם כל הספריות שתתקינו (FastAPI הוא Package ענק עם עשרות תת-Packages).
ו-if __name__ == "__main__": פותר את הבעיה מהשיעור הקודם: קוד בדיקה שרץ בכל פעם שמישהו מייבא את הקובץ.
הסבר מהבסיס
Package = תיקייה של מודולים
Package הוא תיקייה שמכילה מודולים (קבצי .py), ובדרך כלל גם קובץ בשם __init__.py.
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
הנקודה מפרידה בין תיקייה למודול, בדיוק כמו / בנתיב:
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" (שם המודול) |
ולכן:
if __name__ == "__main__":
# runs ONLY when this file is run directly, NOT when it is importedשף מבשל במסעדה שלו (הרצה ישירה), ובמקביל מוכר רטבים לחנויות (ייבוא). כשהוא במסעדה, הוא גם מגיש ללקוחות: זה הקוד שבתוך if __name__ == "__main__". כשחנות קונה רוטב, היא מקבלת רק את הרוטב (הפונקציות), ולא ארוחה שלמה שמוגשת אצלה בכוח.
תחביר
project/
├── main.py
└── package_name/
├── __init__.py
└── module_name.py# 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:
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:
import greetings
print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))הרצה 1: python greetings.py
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!הרצה 2: python app.py
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 אמיתי. מבנה:
week-2/store/
├── main.py
└── inventory/
├── __init__.py
├── products.py
└── stock.pyinventory/__init__.py:
from inventory.products import create_product
from inventory.stock import add_stock, remove_stockinventory/products.py:
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:
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"] - amountmain.py:
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
{'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()+ התנאי. אם מישהו ייבא אותו בעתיד, שום דבר לא ירוץ מעצמו.
אם מנסים python inventory/products.py, מקבלים לעיתים ModuleNotFoundError, כי Python מחפשת מודולים מתוך התיקייה של הקובץ שהורץ (inventory/), ולא מתיקיית הפרויקט. הדרך הנכונה להריץ מודול שבתוך Package היא מתיקיית הפרויקט, עם הדגל -m (module) ונקודות במקום סלאשים: python -m inventory.products.
טעות נפוצה
הטעות 1: לכתוב "main" במקום "__main__".
if __name__ == "main":
main()שום דבר לא רץ, ואין שגיאה! הערך הוא "__main__", עם שני קווים תחתונים מכל צד. Logical Error שקט.
הטעות 2: ייבוא מעגלי (Circular Import).
a.py מייבא את b, ו-b.py מייבא את a:
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)שני מודולים שתלויים זה בזה הם סימן לחלוקה לא טובה. מוציאים את החלק המשותף למודול שלישי, ששניהם מייבאים ממנו.
הטעות 3: לשים את כל הלוגיקה בתוך בלוק ה-if.
if __name__ == "__main__":
def calculate(x):
return x * 2
print(calculate(5))עכשיו אי אפשר לייבא את calculate מקובץ אחר, כי היא מוגדרת רק בהרצה ישירה. הפונקציות בחוץ, ורק ההפעלה בפנים.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: מבנה של Package
ציירו את מבנה התיקיות של פרויקט store עם קובץ main.py ו-Package בשם inventory, שבו שני מודולים: products.py ו-stock.py. איזה קובץ חייב להיות בתוך inventory, ולמה?
הצג פתרון
store/
├── main.py
└── inventory/
├── __init__.py
├── products.py
└── stock.py__init__.py מסמן את התיקייה כ-Package — תיקייה של מודולים שאפשר לייבא ממנה. הוא יכול להיות ריק.
תרגיל 2: ייבוא מ-Package
ב-inventory/products.py יש פונקציה create_product(name, price) שמחזירה מילון. כתבו ב-main.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.
תרגיל 3: מה זה name?
בקובץ greetings.py:
def hello(name):
return "Hello, " + name + "!"
print("__name__ is", __name__)מה יודפס בכל מקרה?
- מריצים
python greetings.py. - מריצים
main.py, שבו כתובimport greetings.
הצג פתרון
- בהרצה ישירה:
__name__ is __main__. - בייבוא:
__name__ is greetings— השם של המודול.
Python מגדירה את __name__ לפי איך הקובץ הופעל. זה הבסיס ל-main guard.
תרגיל 4: Main guard
הוסיפו ל-greetings.py (מהתרגיל הקודם, עם הפונקציה hello) בדיקה עצמית שרצה רק כשמריצים את הקובץ ישירות, ולא כשמייבאים אותו. הבדיקה מדפיסה quick self test: ואז את התוצאה של hello("Tester").
את השורה print("__name__ is", __name__) מהתרגיל הקודם מוחקים.
פלט צפוי (python greetings.py):
quick self test:
Hello, Tester!הצג פתרון
def hello(name):
return "Hello, " + name + "!"
if __name__ == "__main__":
print("quick self test:")
print(hello("Tester"))quick self test:
Hello, Tester!כשקובץ אחר עושה import greetings, התנאי False — הבדיקה לא רצה, ורק הפונקציה זמינה.
תרגיל 5: חבילת mathtools
בנו Package בשם mathtools עם שני מודולים: basic.py עם add(a, b), ו-stats.py עם mean(numbers) ו-maximum(numbers). ב-main.py השתמשו בשלושתן.
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])פלט צפוי:
add: 5
mean: 20.0
maximum: 30הצג פתרון
math_project/
├── main.py
└── mathtools/
├── __init__.py
├── basic.py
└── stats.pymathtools/basic.py
def add(a, b):
return a + bmathtools/stats.py
def mean(numbers):
return sum(numbers) / len(numbers)
def maximum(numbers):
return max(numbers)main.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]))סיכום
- Package = תיקייה של מודולים, עם
__init__.pyשרץ בייבוא הראשון ויכול “לחשוף” שמות נוחים. - ייבוא מ-Package:
from package.module import name. הנקודה = מעבר לתוך תיקייה. __name__הוא"__main__"כשהקובץ הורץ ישירות, ושם המודול כשהוא יובא.if __name__ == "__main__": main()— הפעלה רק בהרצה ישירה. פונקציות מגדירים בחוץ.- מודול בתוך Package מריצים מתיקיית הפרויקט עם
python -m package.module.
דוקומנטציה רשמית
- Python Tutorial — Packages — תיקייה עם init.py
- main — Top-level code environment — if name == “main”