2.34. קוד דינמי

שלושה built-ins מקבלים מחרוזת של קוד מקור Python ומריצים אותה: eval(), exec(), ו-compile(). יחד הם מאפשרים לקוד לבנות ולהריץ עוד קוד בזמן ריצה – מה שלעיתים הוא בדיוק הכלי הנכון, ולעיתים קרובות הרבה יותר מקור לבאגים ולפרצות אבטחה.

אזהרה

פונקציות אלה מריצות Python שרירותי. העברת קלט משתמש ל-eval או ל-exec – מחרוזת מקובץ שהמשתמש יכול לערוך, מטען שהתקבל דרך הרשת, ערך שהוקלד בשורת REPL – מאפשרת לקלט הזה לעשות כל מה שהסקריפט הקורא יכול לעשות, עד כדי מחיקת כל קובץ בהתקן. השתמש בהן במכוון, לעולם לא בתוך קוד שרץ אוטומטית, ולעולם לא על נתונים שאינך שולט בהם.

2.34.1. eval

eval() מריץ ביטוי יחיד ומחזיר את ערכו:

>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'

הביטוי רואה את ה-globals וה-locals של הקורא כברירת מחדל, ולכן name נפתר בדוגמה השנייה. העברת מילונים מפורשים מאפשרת לך לבודד (sandbox) את ההערכה:

eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})

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

2.34.2. exec

exec() מריץ בלוק של קוד ולא ביטוי יחיד – הצהרות, הגדרות פונקציות, לולאות – ומחזיר None:

exec("for i in range(3): print(i)")

פלט:

0
1
2

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

2.34.3. compile

compile() הופך מחרוזת מקור ל-אובייקט קוד שניתן להעביר ל-eval() או ל-exec() מאוחר יותר. השתמש בו כאשר אותו מקור ירוץ פעמים רבות – הניתוח (parsing) מתרחש פעם אחת, הביצוע מהיר יותר:

expr = compile("x * x", "<expr>", "eval")
for x in range(5):
    print(eval(expr))

פלט:

0
1
4
9
16

הארגומנט האמצעי הוא תווית המופיעה ב-tracebacks אם הקוד מעלה חריגה. הארגומנט השלישי הוא "eval" עבור ביטוי יחיד, "exec" עבור בלוק, או "single" עבור הצהרה בסגנון אינטראקטיבי שמדפיסה את תוצאתה.

2.34.4. מתי לפנות לאלה

כמעט אף פעם. לרוב מקרי השימוש שמתחילים מדמיינים יש חלופות בטוחות יותר:

  • קריאת קובץ תצורה. השתמש ב-json – נתונים מובנים, ללא ביצוע.

  • הערכת ערך מספרי שהוקלד על ידי המשתמש. השתמש ב-int() / float() כדי לנתח, ואז אריתמטיקה. אם המשתמש באמת זקוק להזין נוסחה, השתמש במנתח ביטויים קטן, לא ב-eval.

כאשר אתה כן זקוק ל-eval / exec / compile, בודד את אתר הקריאה, רשום ביומן את המחרוזת המדויקת שעומדת להתבצע, והתייחס למקור כאל הדבר החשוד ביותר בקוד שלך.