6 min. read

פרק קודם:

פייתון 19 - למבדות

מה נלמד

  • מהו Decorator.
  • כיצד כותבים אחד
  • מה זה *args, **kwargs

עטיפה של פונקציות

יש לנו פונקציה SayHi שמה שהיא עושה זה מדפיסה Hello World.

1
2
def SayHi():
print("Hello World")

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

1
2
3
4
def SayHi():
print("Hello World")

SayHi()

אנחנו רוצים שתי הדפסות:

Calling Func SayHi
Hello World

שלב א

השלב הראשון זה להדפיס את שם הפונקציה לפני הקריאה:

1
2
3
4
5
def SayHi():
print("Hello World")

print(f"Calling Func {SayHi.__name__}")
SayHi()

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

שלב ב

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

1
2
3
4
5
6
7
8
def LogCall(func):
print(f"Calling Func {func.__name__}")

def SayHi():
print("Hello World")

LogCall(SayHi)
SayHi()

שלב ג

הוספנו קריאה נוספת והמטרה שלנו היא למזער את כמות השינויים.
מה שאפשר לעשות זה להכניס את הקריאה של SayHi לתוך LogCall.

1
2
3
4
5
6
7
8
def LogCall(func):
print(f"Calling Func {SayHi.__name__}")
func()

def SayHi():
print("Hello World")

LogCall(SayHi)

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

שלב ד

המטרה שלנו היא ש-SayHi תדפיס גם את שם הקריאה כשקוראים ישירות ל-SayHi.
כדי לשנות את סדר הקריאות אנחנו צריכים להשתמש בפונקציה חדשה, נקרא לה InternalLog והיא תהיה פנימית בתוך LogCall.

1
2
3
4
5
def LogCall(func):
def InternalLog():
print(f"Calling Func {SayHi.__name__}")
func()
return InternalLog

מה שהפונקציה הפנימית מבצעת זה הדפסה וקריאה לפונקציה המקורית שלנו SayHi.
הפונקציה LogCall כעת מחזירה את הפונקציה הפנימית.

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

1
InternalLog()

שלב ה

כעת אנחנו רוצים לשנות את סדר הקריאות לפונקציה המקורית.

1
2
3
4
5
def SayHi():
print("Hello World")
SayHi = LogCall(SayHi)

SayHi()
  1. אנחנו משנים את SayHi.
  2. כל קריאה ל-SayHi תיקרא קודם ל-InternalLog.
  3. לאחר מכן InternalLog תקרא ל-SayHi המקורית.

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

Decorators

1
2
3
4
5
6
7
8
9
10
11
def LogCall(func):
def InternalLog():
print(f'Calling Func {func.__name__}')
func()
return InternalLog

@LogCall
def SayHi():
print("Hello World")

SayHi()

Decorator - פונקציה שעוטפת או “מקשטת” פונקציה אחרת ומוסיפה התנהגות או משנה אותה.

תנסו להריץ את הקטע קוד הזה ותראו מה קורה.
הפונקציה שנקראת היא SayHi
ומוחזר הערך והוא מודפס.
אך יש משהו שונה בקריאה הזו הפעם - יש לה דקורציה נוספת של פונקציה שנקראת LogCallשמדפיסה את שם הפונקציה המקורית.

הדקורציה לפונקציה היא בעזרת הסמל @ וזה נראה ככה:

1
2
@LogCall
def SayHi():

כדי להבין את זה צריך לשים לב ל-2 דברים כאן:

  1. הגדרה של פונקציה העוטפת פונקציה אחרת
1
2
3
4
5
def LogCall(func):
def InternalLog():
print(f'Calling func {func.__name__}')
func()
return InternalLog

אנחנו מגדירים פה שתי פונקציות נוספות:
LogCall - הפונקציה המקשטת שעוטפת פונקציות אחרות.
InternalLog - הפונקציה שמבצעת את ההתנהגות וקוראת לפונקציה המקורית.

אחרי שמוסיפים את הדקורציה LogCall לפונקציה SayHi אנחנו משנים את סדר הקריאות.

  1. החזרה של פונקציה
    כמו שלמדנו בלמבדות - אנחנו יכולים להחזיר פונקציה מפונקציה אחרת כך שסדר הקריאות משתנה.
    הפונקציה הפנימית InternalLog מוחזרת במקום הפונקציה המקורית SayHi.

הקריאה הזו:

1
SayHi()

בעצם קוראת לפונקציה InternalLog שהיא בתורה קוראת לפונקציה המקורית SayHi.

תרגיל

  1. תתקנו את קטע הקוד הבאה כך שיודפס “Correct” וה-Decorator יהיה נכון.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
global number
number = 0

def DoTwice(func):
def Inner():
func()
return Inner

@DoTwice
def CallMe():
global number
number += 1
if number == 2:
print("Correct")


CallMe()
  • שימו לב ה-global הופך את המשתנה לגלובאלי ככה שאפשר לקרוא לו מכל התכנית.
    זה לא נפוץ ולרוב לא כדאי להשתמש בו - רק לצורך התרגיל.

קבלה והחזרה של פרמטרים

1
2
3
4
5
6
7
8
9
10
11
def LogCall(func):
def Log(*args, **kwargs):
print(f'Calling Func {func.__name__}')
return func(*args, **kwargs)
return Log

@LogCall
def Add(a,b) -> int:
return a + b

print(Add(1,2))

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

1
*args, **kwargs

וכדי להחזיר את הערך שמתקבל כל מה שעלינו לעשות זה לקרוא ל-return:

1
return func(*args, **kwargs)

מה זה *args

*args משתמש באופרטור הכוכבית.
הכוכבית בפרמטר נותן לנו אופציה להעביר יותר מפרמטר אחד לפונק’ מבלי להגדיר רשימה כלשהי.

1
2
3
4
5
6
7
8
def Sum(*arguments) -> int:
sum = 0
for num in arguments:
sum += num
return sum

print(Sum(1,2,3))
print(Sum(22,24,66,-22))

פייתון בעצם מתייחס לערך הזה כ-Tuple. ניתן לצפות בזה או בדיבאג או עם הדפסה:

1
print(f"Type of arguments: {type(arguments)}")

זוכרים Tuple? יוצרים אחד בעזרת הסוגרים המעוגלים! ().

מה זה **kwargs

פייתון מאפשר גם להעביר פרמטרים בעזרת שם:

1
2
3
4
def SayHiTo(name):
print(f"Hello {name}")

SayHiTo(name = "Bob")

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

1
2
3
4
5
6
7
def PrintData(**data):
print(f"Type is {type(data)}:")

for k, v in data.items():
print(f"{k} = {v}")

PrintData(Name = "Bob", Age = 44)

במקרה הזה נוצר לנו מילון!

מבינים למה יש לנו משתנים עם שמות ובלי שמות?

חזרה לדוגמא שלנו

1
2
3
4
5
def LogCall(func):
def InternalLog(*args, **kwargs):
print(f'Calling Func {func.__name__}')
return func(*args, **kwargs)
return InternalLog

כאשר אנחנו מעבירים את שניהם לפונקציה המקורית בתוך ה-Decorator אנחנו בעצם מעבירים את כל הפרמטרים עם שם ובלי שם.

אמ;לק
כוכבית אחת יוצרת Tuple לפרמטרים ללא שם.
שתי כוכביות יוצרות מילון לפרמטרים עם שם.

שימוש בכמה Decorators

ניתן להשתמש בכמה Decoratorים על פונקציה אחת, זה לא עסק מורכב וכל מה שאתם צריכים לעשות זה להוסיף אותם בסדר יורד:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def LogBefore(func):
def Inner():
print(f"Before {func.__name__}")
func()
return Inner

def LogAfter(func):
def Inner():
func()
print(f"After {func.__name__}")
return Inner


@LogAfter
@LogBefore
def SayHi():
print("Hello")

SayHi()

במקרה הזה זה כמו לכתוב:

1
SayHi = LogAfter(LogBefore(SayHi))

תנסו להריץ את זה - תראו מה קורה!


תרגילים

  1. כתבו פונקציית קישוט אשר סופרת כמה פעמים הפונקצייה נקראית.
  • טיפ: תשתמשו במילון ובקבלת השם של הפונקציה ע”י
    1
    func.__name__

2.
כתבו פונקציית קישוט לפונקצייה MyFunc אשר תדפיס את כמות הפרמטרים שהועברו אליה.

1
2
3
4
5
def MyFunc(*args):
for arg in args:
print(arg)

MyFunc('Hello',' World')
  1. שמנו כובע Gray-Hat ואנחנו רוצים להרוס את פונקציית הקישוט הבאה כך שכל מחרוזת שהפונקציה המקורית מקבלת כל אות קטנה תהפוך לגדולה וכל אות גדולה תהפוך לקטנה!
    להלן קטע קוד לעזר:
    ניתן להניח שכל הפרמטרים הם מחרוזות.
1
2
3
4
5
6
7
8
9
10
11
12
def ValidateArguments(func):
def InnerFunc(*args, **kwargs):
# Do here
return func(*args, **kwargs)
return InnerFunc

@ValidateArguments
def SayHi(names):
for name in names:
print(f"Hello {name}")

SayHi({"Bob", "Moshe", "Lior"})




השני החלק עובר על שימושים נפוצים בפייתון בעזרת ה-Decoratorים.

פייתון 21 - פונקציות קישוט חלק ב

אהבתם? מוזמנים להביע תמיכה כאן: כוס קפה