ספריה במקום הבנה?
פרויקט פייתון חדש מציע לפתור עבורנו את הבעיה הקלאסית עם קלוז'רים בפייתון. זה קוד הדוגמה:
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 כמו שרצינו, וכמובן יש לו עוד שני יתרונות חשובים:
לא צריך להתקין כלום, לא הוספתי תלות חדשה לפרויקט.
הקוד משתמש במבנה יותר גנרי - הפונקציה 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
ועדכן אותה לרוץ עם המשתנים "שנתפסו" אוטומטית. ועדיין התער של אוקהם מנצח פה. להבין את השפה יותר משתלם מאשר לשבור אותה כדי שתתאים למודל המנטלי שלך.