שבוע 9 – יום 3: רמיזות על טיפוסים (Type Annotations)

רמיזות על טיפוסים – Type Annotations

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

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

אבל זה לא נגמר כאן! ל־type annotations יש שני יתרונות פרקטיים משמעותיים מאוד:

  1. ה־IDEs שלכם יודעים לזהות את ה־type annotation, ולהגיד לכם כשהעברתם סוג משתנה לא נכון או כשאתם מכניסים את עצמכם לפה של אריה.
  2. יש כלים חיצוניים, כמו mypy, שמאפשרים לכם לדעת האם יש חוסר עקביות בסוגי המשתנים. לדוגמה, אם הצהרתם בעזרת type annotations שמפונקציה מסוימת אמור לחזור מילון, וניסיתם לעשות על הערך שחזר ממנה .strip(), הכלי החיצוני יצעק שאין פעולה כזו על מילון.

טכניקה

מספיק תיאוריה לבינתיים. בואו נראה איך זה נראה. ניקח את הקוד הבא, שבו אין type annotations:

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

ונוסיף לו type annotations:

def add(a: int, b: int) -> int:
    return a + b

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

קראו עוד בכללי על mypy כאן.

סיבוכים

אבל רגע, מה קורה אם אנחנו רוצים לקבל int או float? ניתן להשתמש ב־Union מתוך ספריית typing:

from typing import Union


def add(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
    return a + b

אפשר גם לשמור על משתנה לשם הקריאות:

from typing import Union


Number = Union[int, float]


def add(a: Number, b: Number) -> Number:
    return a + b

עבור משתנים מטיפוסים יותר מורכבים, כמו List, Tuple, Dict וכדומה, יש Type annotations יותר מורכבים.

במילון מציינים את הטיפוס של המפתח, ואז את הטיפוס של הערך:

ages: Dict[str, int] = {"Yam": 28, "Nimrod": 29, "Lukas Graham": 7}

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

bands: List[str] = ["Caravan Palace", "C2C", "Deluxe"]

ב־tuple מציינים את הטיפוס של כל אחד מהמקומות ב־tuple.
אם לא ידוע כמה איברים נמצאים ב־tuple, יש להשתמש ב־...:

bands: Tuple[str, str, str] = ("Caravan Palace", "C2C", "Deluxe")

או:

bands: Tuple[str, ...] = ("Caravan Palace", "C2C", "Deluxe")

קריאה מעמיקה של הדף הזה תסדר לכם את כל החומר שצריך לדעת בנושא.
נוסף על הרעיונות שהוצגו כאן, ודאו שאתם יודעים להשתמש ב־Callable, Sequence, Iterable, Iterator, Optional ו־Any.


כך או כך, type annotations הם רשות ופייתון עצמו מתעלמת מהם לחלוטין. אפילו אם תוסיפו Type annotations שאומר שפרמטר של פונקציה מסוימת הוא bool, פייתון לא תחייב אתכם לשלוח אליה ארגומנט מסוג bool ובפועל תוכלו לעשות מה שאתם רוצים. זה כלי עזר לא מחייב.


הפעלת mypy

הכלי mypy פותח על ידי Guido Van Russom (כן, זה הבחור שפיתח את פייתון במקור).
מטרתו הוא לנתח קוד שיש בו type annotations ולבדוק שהתוכנה תקינה ואין “התנגשויות” או בעיות במהלך ההצהרות על טיפוסי המשתנים.

כדי להריץ אותו, התקינו את mypy בעזרת pip install.
לאחר מכן, פתחו שורת פקודה והקלידו בה mypy ואז את הנתיב לפרויקט/לקובץ שלכם:

❯ mypy file.py
Success: no issues found in 1 source file

ניתן לכתוב אחרי המילה mypy את הדגל --strict, שמחייב את כל הקוד להיות type annotated (כדי שלא נשכח), ומוסיף בדיקות מעט מחמירות יותר על טיפוסי הנתונים. לדוגמה:

❯ mypy --strict sounds
sounds/dog.py:1: error: Function is missing a return type annotation
sounds/dog.py:1: note: Use "-> None" if function does not return a value
sounds/cat.py:1: error: Function is missing a return type annotation
sounds/cat.py:1: note: Use "-> None" if function does not return a value
sounds/koo/mmm.py:3: error: Call to untyped function "make_sound" in typed context
Found 3 errors in 3 files (checked 5 source files)

נסו להריץ mypy עם --strict על אחד התרגילים שלכם כדי להתרשם בעצמכם.


תרגילים

טיפוס אפל

הספרים בספרייתו של רוספורף פיינס כושפו בכישוף נורא: כל המילים בספר התהפכו (“שלום” הפך ל"םולש").
חשבתם שכאן תם ונשלם הקונדס? לא ולא! במקום כל אות מופיע הערך הגימטרי שלה.

לדוגמה: במשפט “מי ימלל היפוגריף שאוכל?” סדר האותיות התהפך: “ימ ללמי ףירגופיה ?לכואש”, ואז כל אות הומרה לערך הגימטרי שלה:

010040000030030040010000080010200003006080010005000?030020006001300

שימו לב שכל אות תופסת 3 תווים בדיוק (האות א’ מיוצגת כ־001, האות מ’ כ־040 וכד’).
במקום רווח הוכנס המספר 000, ושאר התווים שאינם עברית נשארו כפי שהיו במקור.

כתבו פונקציה שמקבלת ספר של רוספורף פיינס (מחרוזת טקסט) ויודעת לשחזר אותו למצבו המקורי.
דאגו לכתוב type annotations.

לנוחיותכם: טבלת הגימטריה כמילון.

gematria = {
    'א': 1, 'ב': 2, 'ג': 3, 'ד': 4, 'ה': 5, 'ו': 6, 'ז': 7, 'ח': 8, 'ט': 9,
    'י': 10, 'כ': 20, 'ל': 30, 'מ': 40, 'נ': 50, 'ס': 60, 'ע': 70, 'פ': 80,
    'צ': 90, 'ק': 100, 'ר': 200, 'ש': 300, 'ת': 400,
    'ך': 20, 'ם': 40, 'ן': 50, 'ף': 80, 'ץ': 90,
}

מט־פיי

הוסיפו type annotations לתרגיל השחמט משבוע שעבר.
ניתן להוסיף את ה־type annotations אחרי שחילקתם את התרגיל לקבצים.


משאבים נוספים:

  1. מאמר סופר מפורט ב־Real Python
  2. התיעוד של הספרייה typing.
  3. התיעוד של mypy.
7 לייקים