כשאני לא יודע לכתוב Type Hint בפייתון

24/03/2024

מנגנון Type Hints בפייתון הוא אחד המנגנונים הכי חשובים לבניית קוד קריא וממשק יציב. אבל באותה נשימה צריך גם להגיד שהוא "הולבש" על השפה באיחור, ויהיו מצבים שלמרות כל הרצון הטוב ניתקע ולא נדע איך לכתוב את ה Type Hint הטוב ביותר.

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

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

def random_weighted_item(items):
    items = sorted(items, key=lambda x: x.weight)
    min_weight = min(i.weight for i in items)
    max_weight = max(i.weight for i in items)
    normalized_weights = [(i.weight - min_weight) / (max_weight - min_weight) for i in items]
    cumulative_sum = list(accumulate(normalized_weights))
    randomized_weight = random.random() * cumulative_sum[-1]
    index = next(i for i, e in enumerate(cumulative_sum) if e > randomized_weight)
    return items[index]

אפשר להוסיף לזה בקלות Type Hints בעזרת Type Var וזה יראה כך:

class HasWeight(Protocol):
    weight: int

T = TypeVar("T", bound=HasWeight)

def random_weighted_item(items: list[T]) -> T:
    ...

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

def random_weighted_item(items: list[T], weight: Callable[[T], int]) -> T:
    ...

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

def random_weighted_item(items: list[T], weight: Callable[[T], int] = lambda i: i.weight) -> T:

הודעת השגיאה היא:

weighted_random_demo.py:44: error: "T" has no attribute "weight"  [attr-defined]
Found 1 error in 1 file (checked 1 source file)

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

  1. לבחור חתימה שעובדת יותר טוב עם ה Type Hints (למשל כמו choices שמקבלת רשימה של פריטים ורשימה של משקלים)

  2. לוותר על ה Type Hints ולהתעקש על החתימה שבחרנו.

ההתלבטות קשה. בדוגמה כאן עדיף לשנות את החתימה כי החתימה של choices באמת יותר ברורה. במקרה הכללי יהיו גם מצבים שנעדיף לשים בצד את ה Type Hint ולחזור להוסיף אותו בהמשך, או כשאנחנו נדע יותר על Type Hints או כשהמנגנון ישתפר בגירסה חדשה יותר של פייתון.

נ.ב. אם אתם מכירים פיתרון של Type Hints לחתימה מהדוגמה אשמח לשמוע בתגובות או בטלגרם.