Menu

if __name__ == '__main__' ב-Python: הסבר על ה-Main Guard

מה באמת עושה if __name__ == '__main__', למה כל סקריפט Python רציני מגיע בסוף לדפוס הזה, ואיך לבנות קובץ שמשמש גם כסקריפט וגם כמודול.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

השורה המוזרה ביותר ב-Python, בלי מסתורין

פתחו כמעט כל קובץ Python, ובסופו של דבר תראו את זה בתחתית:

if __name__ == "__main__":
    main()

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

הרקע: שתי דרכים להשתמש בקובץ

כל קובץ .py ב-Python אפשר להשתמש בו בשתי דרכים שונות:

  1. כסקריפט: מריצים אותו ישירות עם python3 file.py.
  2. כמודול: קובץ אחר מייבא אותו עם import file.

השפה לא מחייבת להצהיר איזה מהשניים אתם כותבים. כל קובץ עשוי בסוף לעשות את שניהם.

נניח קובץ בשם math_helpers.py:

# math_helpers.py
def mean(values):
    return sum(values) / len(values)

def median(values):
    s = sorted(values)
    mid = len(s) // 2
    if len(s) % 2 == 0:
        return (s[mid - 1] + s[mid]) / 2
    return s[mid]

# בדיקה מהירה בזמן הפיתוח
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("mean:", mean(numbers))
print("median:", median(numbers))

אם תריצו את הקובץ הזה ישירות, הוא ידפיס את פלט הבדיקה, מצוין. אבל אם קובץ אחר עושה import math_helpers, פקודות ה-print האלה רצות בזמן הייבוא. כל מי שמשתמש במודול רואה את פלט הניסויים שלכם. לא מצוין.

מה זה __name__

לכל מודול Python יש משתנה מובנה בשם __name__. Python מגדירה אותו אוטומטית:

  • כשהקובץ מיובא, __name__ מקבל את שם הייבוא של המודול: "math_helpers" בדוגמה שלמעלה.
  • כשהקובץ מורץ ישירות, Python מגדירה את __name__ למחרוזת המיוחדת "__main__".

זה נותן לכם דרך לדעת באיזה מצב אתם נמצאים. עטפו את הקוד "של הסקריפט בלבד" בבדיקה:

# math_helpers.py
def mean(values):
    return sum(values) / len(values)

def median(values):
    s = sorted(values)
    mid = len(s) // 2
    if len(s) % 2 == 0:
        return (s[mid - 1] + s[mid]) / 2
    return s[mid]

if __name__ == "__main__":
    numbers = [3, 1, 4, 1, 5, 9, 2, 6]
    print("mean:", mean(numbers))
    print("median:", median(numbers))

עכשיו python3 math_helpers.py מדפיס את פלט הבדיקה. אבל import math_helpers מקובץ אחר מדלג על הבלוק לגמרי: הפונקציות זמינות, ושום דבר לא רצוי לא רץ.

הבן הלוויה הנפוץ: פונקציית main()

רוב הסקריפטים האמיתיים מקבלים בסוף פונקציית main() ייעודית ושומרים על בלוק ה-guard זעיר:

# process_orders.py
import sys

def load_orders(path):
    with open(path) as f:
        return [line.strip() for line in f if line.strip()]

def process(orders):
    for order in orders:
        print(f"Processing {order}")

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 process_orders.py <file>")
        sys.exit(1)
    orders = load_orders(sys.argv[1])
    process(orders)

if __name__ == "__main__":
    main()

למה לטרוח עם main() במקום לשים את הלוגיקה ישירות מתחת ל-guard של ה-if?

  1. בדיקוּת. אפשר לייבא את המודול ולקרוא ל-main() (או לכל אחת מפונקציות העזר) מתוך בדיקות.
  2. משתנים מקומיים. כל מה שמוגדר בתוך main() הוא משתנה מקומי, לא שם ברמת המודול. זה מונע התנגשויות שמות בטעות ומשאיר את הממשק הציבורי של המודול נקי יותר.
  3. קריאוּת. בלוק ה-guard הוא עכשיו שתי שורות: "נקודת כניסה לסקריפט" ברורה במקום ערימת לוגיקה.

הדגמה מהירה

אפשר לראות את ההתנהגות ישירות:

כשמריצים את הקטע שלמעלה עם Python, הוא מדפיס __name__ is: __main__ ואת ההודעה "being run as a script". אם קובץ אחר היה מייבא את הקובץ הזה, אותה בדיקה הייתה מדפיסה את הענף השני.

מתי לא צריך את ה-Guard

הרבה קובצי Python אף פעם לא מקבלים אחד. סקריפטים קצרים שאף אחד לא ייבא אף פעם מסתדרים מצוין בלעדיו:

# one_off_rename.py
import os
for name in os.listdir("."):
    if name.endswith(".txt"):
        os.rename(name, name.lower())

זה טוב לגמרי. אתם לא בונים ספרייה, ואף אחד לא ייבא את זה. אבל ברגע שיש בקובץ לוגיקה שאפשר לעשות בה שימוש חוזר, ואתם רוצים להשאיר לצידה קוד עם תופעות לוואי, השתמשו ב-guard.

נקודות כניסה לחבילות מותקנות

כשמפרסמים את הקוד כחבילה אמיתית, יש מנגנון רשמי יותר: entry_points במטא-דאטה של החבילה, שממפים פקודה לפונקציה. משתמשים שמתקינים את החבילה מקבלים פקודת shell שקוראת ישירות לפונקציה. אבל לסקריפטים שרצים מתוך תיקייה במחשב שלכם, ה-guard של __main__ הוא הכלי הסטנדרטי.

סיכום הפרק

עכשיו יש לכם:

  • פונקציות כדי לתת שם ללוגיקה.
  • *args/**kwargs לקריאות גמישות.
  • lambdas לפונקציות חד-פעמיות.
  • Decorators ו-type hints כדי לעטוף ולתעד את הפונקציות האלה.
  • מודולים, pip וסביבות וירטואליות כדי לפצל קוד בין קבצים ופרויקטים.
  • ה-guard של __main__ כדי לאפשר לסקריפטים ולמודולים לחיות יחד.

הצעד הבא: מחלקות

עד עכשיו חיברתם בעיקר פונקציות לנתונים פשוטים: dicts, רשימות, tuples. הפרק הבא מציג מחלקות, הדרך של Python לקשור נתונים והתנהגות יחד, והמקום שבו חיים מושגים כמו self, __init__, ירושה ו-dataclasses.

שאלות נפוצות

מה עושה if __name__ == '__main__'?

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

האם Python צריכה פונקציית main?

לא. קובצי Python רצים אוטומטית מלמעלה למטה. אבל ברגע שיש בקובץ לוגיקה שרוצים שאפשר יהיה גם לייבא, ה-guard if __name__ == '__main__' הופך לחשוב, והכנסת העבודה הראשית של הסקריפט לתוך פונקציית main() היא הרגל נפוץ שמתלווה אליו.

מה ההבדל בין __name__ ל-__main__?

__name__ הוא משתנה מיוחד ש-Python מגדירה בכל מודול. אם הקובץ מיובא, __name__ הוא שם המודול (כמו 'util'). אם הקובץ מורץ ישירות, Python מגדירה את __name__ למחרוזת '__main__'. בדיקת ה-if משווה בין השניים.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל