איך decorators עובדים (פייתון)



מה זה decorators?

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

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

הסינטקס פשוט מאוד ויראה כך:

הדקורטורים בפייתון עובדים כמו ומממשים עיקרון דומה ל decorator design pattern (שבדרך כלל נראה בשימוש במחלקות)

מתי נצטרך decorators?

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

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

ואותה שורת לוג הייתה נמצאת לנו בכל פונקציה

אבל דקורטרים נותנים לנו יכולת לגרום לאותה פונקציונאליות להיות הרבה יותר אלגנטית, פחות משוכפלת ולהראות כך:

איך decorators עובדים בפייתון?

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

first class function

הקונספט של first class function מאפשר לנו להשתמש בפונקציות כ-first class citizen. כלומר, מאפשר לנו להעביר פונקציות כפרמטר לפונקציות אחרות, לעשות השמה של פונקציות לתוך משתנה, להחזיר אותם כערך מפונקציה ואפילו למיין אותם במערך.

לדוגמה,
אפשר לראות שביצענו כאן השמה של הפונקציה first_class לתוך func_var, ואז הרצנו את func_var ממש כפונקציה.

closures

הקונספט של כתיבת פונקציה בתוך פונקציה כך שהפונקציה הפנימית מכירה וזוכרת משתנים מתוך הפונקציה החיצונית גם לאחר ההרצה.
בדוגמאות הקוד אפשר לראות שיש לנו פונקציה בשם inner_print.
הפונקציה inner_print מוגדרת בתוך הפונקציה print_msg ומשתמשת במשתנה msg מבלי לקבל אותו.

בדוגמה השניה אנחנו רואים את אותו הקונספט בדיוק פשוט עם פרמטר שמתקבל מבחוץ (שימו לב שהפרמטר עדיין לא מגיע ישירות לתוך inner_print).

דוגמה למימוש decorators בפייתון

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

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


כמו שניתן לראות הפונקציה decorator היא הפונקציה החיצונית והיא מקבלת אליה פונקציה כפרמטר. בתוך הפונקציה החיצונית אנחנו מגדירים פונקציה פנימית שמטרתה לעטוף ולהוסיף פונקציונאליות לפונקציה השקיבלנו כפרמטר (original_func). ובסוף אנחנו מחזירים את הפונקציה ה״ערוכה״.

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

כלומר:
decorated = decorator(do_stuff)
decorated()

יהיה שקול לפשוט לשים ()decorator@ מעל הפונקציה do_stuff ואז פשוט לקרוא ל do_stuff באופן ישיר.
הדרך השניה כמובן אלגנטית בהרבה.

decorator עם פרמטרים

מה קורה אם הפונקציה do_stuff שראינו למעלה הייתה מקבלת פרמטר?
דקורטור כמובן יכול לקבל אליו גם פרמטרים ולהשתמש בהם, ואת זאת יהיה נכון לעשות באמצעות שימוש ב args ו kwargs שמאפשרים לנו שליחת פרמטרים גמישה ודינאמית אל הפונקציה אותה אנחנו רוצים להפעיל.

המימוש במקרה הזה יראה כך:


במקרה הזה פשוט העברנו פרמטרים לפונקציה do_stuff, הפרמטרים התקבלו על ידי args ו- kwargs בפונקציית ה wrapper (הפנימית) והועברו לפונקציה.


דקורטור להדפסת לוגים

באמצעות אותו העקרון בדיוק שהצגנו כעת נוכל לכתוב את הדקורטור של log באופן הבא:

סיכום

דקורטורים הם קונספט עוצמתי ודי פשוט למימוש והבנה. באמצעותם, אנחנו יכולים להקל מאוד על השימוש בקוד ועל קריאותו. בקוד-בייס שאני עובד עליו לדוגמה אנחנו משתמשים הרבה בדקורטורים כמו retry (על מנת להתגבר על בעיות רשת), log_times (שמדפיס בדיוק כמה זמן הפונקציה אותה עוטף לקחה), timeout (שמגביל את הרצת הפעולה אותה הוא עוטף בזמן) ועוד ועוד..

נתראה בפוסט הבא!

תגובות

  1. אהבתי את הדוגמה האחרונה שלך, אבל חושב שפשוט מאוד מימשת את זה בצורה שמישהו חדש לא יבין/ יחשוב שככה כותבים logים בפייתון, דבר שעשוי להטעות ציבור רחב.
    הייתי מציע לתקן את הכמה שורות קוד האלו בתוך ה-decorator wrapper הפנימי ולשים את הכמה שורות הקבועות של המודיול logging:
    logging.Logger('Main')
    logging.Formatter("...")
    logging.basicConfig(....)
    logging.X("....")

    השבמחק
  2. מה ההבדל בין *args ל *kwargs?

    השבמחק

הוסף רשומת תגובה

פוסטים פופולריים מהבלוג הזה

תכנות מונחה עצמים | Dependency Inversion Principle

מהם קבצי DLL ואיך להשתמש בהם?

מה ההבדל בין אוטומציה לפיתוח רגיל