• בלוג
  • ספריה במקום הבנה?

ספריה במקום הבנה?

20/09/2023

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

a = []
for i in [1, 2]:
    a.append(lambda: i)
for f in a:
    print(f())

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

וכן דרך אחת זו הפונקציה מהפרויקט scope_capture.

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

from functools import partial

a = []
for i in [1, 2]:
    a.append(partial(lambda i: i, i))
for f in a:
    print(f())

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

  1. לא צריך להתקין כלום, לא הוספתי תלות חדשה לפרויקט.

  2. הקוד משתמש במבנה יותר גנרי - הפונקציה partial. מתכנתים חדשים שיגיעו לקוד אולי ילמדו להשתמש בה גם בהקשרים אחרים.

נשווה את זה לקוד של הספריה scope_capture:

import inspect
from types import FunctionType

def _make_cell(value):
    fn = (lambda x: lambda: x)(value)
    return fn.__closure__[0]

def capture(f):
    try:
        frame = inspect.currentframe()
        fake_globals = {}
        fake_globals.update(f.__globals__)
        fake_globals.update(frame.f_back.f_locals)
        captured_cells = []
        if f.__closure__:
            for cell in f.__closure__:
                captured_cells.append(_make_cell(cell.cell_contents))
        call_fn = FunctionType(
            code=f.__code__,
            globals=fake_globals,
            name=f.__name__,
            argdefs=f.__defaults__,
            closure=tuple(captured_cells),
        )
        return call_fn
    finally:
        del frame

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