Menu

Type Hints ב-Python: אנוטציות לפונקציות, רשימות, Dicts ועוד

מה הם type hints ב-Python, מתי הם עוזרים, והתחביר לאנוטציה של משתנים, חתימות פונקציות, מכולות וערכים אופציונליים.

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

אנוטציות שמתארות, לא אוכפות

type hint הוא הערה שמצמידים לשם, בדרך כלל לפרמטר של פונקציה, שאומרת "זה צריך להיות int", "זה מחזיר רשימה של מחרוזות" וכן הלאה. Python לא בודקת אותם בזמן ריצה. העברת מחרוזת במקום שבו כתבתם int לא זורקת שגיאה. העורך שלכם וכלים חיצוניים (mypy, pyright, ו-Pylance של VS Code שמבוסס על Pyright) קוראים את הרמזים ומזהירים אתכם לפני שהקוד רץ.

המקרה הפשוט ביותר האפשרי:

name: str היא אנוטציה לפרמטר. -> str היא אנוטציה לערך המוחזר. שתי הקריאות רצות. השנייה שגויה, ובודק טיפוסים סטטי היה מסמן אותה, אבל Python עצמה מעבדת אותה בשמחה כי 42 במקרה תומך בשילוב בתוך f"{...}".

זה המודל המחשבתי החשוב: רמזים הם תיעוד שמכונה יכולה לקרוא. הם לא משנים את זמן הריצה.

למה לטרוח?

שלושה יתרונות מוחשיים, לפי הסדר שבו הם משתלמים:

  1. העורך שלכם נהיה חכם יותר. השלמה אוטומטית מציגה את המתודות הנכונות, שינויי שמות מתפשטים נכון, וריחוף מעל משתנה מציג את הטיפוס שלו.
  2. חתימות פונקציות מתארות את עצמן. def fetch(url: str, timeout: float = 5.0) -> dict: אומרת לקורא בדיוק מה להעביר ומה יקבל בחזרה, בלי צורך לקרוא את הגוף.
  3. בודקי טיפוסים תופסים טעויות לפני שמריצים את הקוד. הרצת mypy . על פרויקט חושפת באגים מהסוג שבדיקות יחידה מפספסות לא פעם: None שמוחזר במקום שציפיתם לערך, dict שמשמש במקום שבו צריכה להיות רשימה.

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

טיפוסים מובנים בסיסיים

לא צריך import לאף אחד מאלה:

אנוטציות למשתנים (name: str = "Rosa") נחוצות רק לעיתים רחוקות: Python מסיקה את הטיפוס מהערך שמושם. שמרו אותן לפרמטרים, לטיפוסי החזרה ולמקרים הנדירים שבהם הטיפוס שהוסק דו-משמעי.

פונקציות שלא מחזירות כלום משתמשות ב--> None:

רשימות, Dicts, Tuples ו-Sets

מכולות צריכות פיסת מידע נוספת: מה הן מכילות. Python מודרנית מאפשרת לכתוב סוגריים מרובעים ישירות על הטיפוסים המובנים:

כך קוראים אותם בקול:

  • list[float]: רשימה של floats.
  • dict[str, int]: dict עם מפתחות מחרוזת וערכי int.
  • tuple[float, float]: tuple של בדיוק שני floats.
  • set[str]: set של מחרוזות.

התחביר list[...], dict[...] עובד ב-Python 3.9 ואילך. בקוד ישן יותר תראו List, Dict, Tuple שמיובאים מ-typing: אותה משמעות, כתיבה ישנה יותר.

ערכים אופציונליים

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

str | None פירושו "מחרוזת, או None". התחביר | עובד ב-Python 3.10 ומעלה. בקוד ישן יותר תראו Optional[str] מהמודול typing, שפירושו אותו דבר.

קורא שרואה -> str | None יודע לבדוק אם יש None לפני שהוא משתמש בתוצאה, וזו כל המטרה של האנוטציה.

טיפוסי Union: זה או זה

כשערך יכול להיות אחד מכמה טיפוסים, השתמשו ב-|:

אפשר לאחד יותר משני טיפוסים. int | str | float פירושו "כל אחד משלושת אלה".

אנוטציה למשתנים בתוך פונקציות

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

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

typing.Any הוא פתח המילוט: "אני לא רוצה לתת לזה אנוטציה מדויקת". השתמשו בו במשורה. שימוש מופרז ב-Any הופך את שאר ה-type hints שלכם לחסרי ערך.

אנוטציה למחלקות

attributes של מחלקה וחתימות של מתודות מקבלים אנוטציה בדיוק כמו כל פונקציה אחרת:

Dataclasses דווקא דורשות אנוטציות טיפוסים: ה-decorator @dataclass קורא אותן כדי לייצר את __init__ ואת __repr__. זה המקום היחיד שבו אנוטציות משפיעות על ההתנהגות בזמן ריצה.

Tuples והמקרה של "כל אורך"

ל-tuple[...] יש שתי צורות שמבלבלות מתחילים:

  • tuple[float, float]: בדיוק שני floats.
  • tuple[int, ...]: כל מספר של ints. ה-... (רכיב תחבירי אמיתי במערכת הטיפוסים) פירושו "וכן הלאה".

Callables וכינויי טיפוסים

כשפונקציה מקבלת או מחזירה פונקציה אחרת, השתמשו ב-Callable:

Callable[[int], int] פירושו "פונקציה שמקבלת int אחד ומחזירה int".

כשאנוטציה חוזרת על עצמה, תנו לה שם:

כינוי הוא פשוט השמה רגילה של Python. בכל מקום שבו הייתם משתמשים בצורה הארוכה, השם הקצר עובד.

הרצת בודק טיפוסים

המפרש של Python עצמו מתעלם מ-type hints. כדי באמת לבדוק אותם, התקינו בודק טיפוסים. mypy הוא המקורי, ו-pyright (שמשמש את Pylance של VS Code) מהיר יותר.

pip install mypy
mypy your_project/

ההרצה הראשונה תחשוף שגיאות במקומות שלא שמתם לב אליהם. עבדו עליהן בהדרגה: # type: ignore משתיק שורה בודדת כשצריך להתקדם.

סביבות פיתוח מודרניות מריצות בדיקת טיפוסים ברציפות תוך כדי עריכה, כך שרוב המשוב מגיע עוד לפני השמירה.

מתי type hints לא מתאימים

  • סקריפטים מהירים לחקירה. אנוטציות מוסיפות חיכוך לקוד שחי שעה.
  • קוד דינמי מאוד. מטא-תכנות, מערכות תוספים ודפוסים דומים חורגים לעיתים קרובות ממה שמערכת הטיפוסים יכולה לתאר. תנו אנוטציה ל-API החיצוני והשאירו את הפנים גמיש.
  • ספריות צד שלישי בלי טיפוסים. אם לספרייה שאתם מייבאים אין מידע על טיפוסים, Any זולג לקוד שלכם. זה בסדר: זה לא הקוד שלכם לתת לו אנוטציות.

לכל מה שבאמצע, type hints הם הרגל קטן עם תמורה גדולה. המחיר הוא כמה הקשות מקלדת נוספות לכל חתימת פונקציה. התמורה היא פחות באגים, refactoring קל יותר וקוד שמתעד את עצמו.

הצעד הבא: מודולים וייבוא

עכשיו יש לכם את כל הכלים ברמת הפונקציה: ארגומנטים, decorators, type hints. בהמשך נראה איך Python מארגנת קוד בין קבצים: מודולים, חבילות ומערכת הייבוא.

שאלות נפוצות

מה הם type hints ב-Python?

type hints הם אנוטציות שמתארות את הטיפוסים הצפויים של משתנים, פרמטרים של פונקציות וערכים מוחזרים. Python עצמה לא אוכפת אותם בזמן ריצה: הם נועדו לכלים (סביבות פיתוח, linters, בודקי טיפוסים כמו mypy או pyright) ולבני אדם שקוראים את הקוד.

האם type hints גורמים ל-Python לרוץ מהר יותר?

לא. מפרש Python מתעלם מ-type hints בזמן ריצה. ההאצה היא בתהליך הפיתוח שלכם: פחות שגיאות הקלדה שהעורך תופס, חתימות פונקציות ברורות יותר ו-refactoring בטוח יותר.

מתי להוסיף type hints?

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

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

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

להתחיל