בעמוד הזה
Higher-Order Functions — פונקציות שמקבלות ומחזירות פונקציות
- להסביר מה זו Higher-Order Function ולמה זה אפשרי ב-Python
- להעביר פונקציה כפרמטר — בלי סוגריים — ולקרוא לה מתוך פונקציה אחרת
- להשתמש ב-map, filter ובפרמטר key של sorted, max ו-min
- לכתוב פונקציה שמחזירה פונקציה
מה נלמד?
- מה זו Higher-Order Function (פונקציה מסדר גבוה).
- איך מעבירים פונקציה כפרמטר — ולמה בלי סוגריים.
- הפונקציות המובנות
mapו-filter. - הפרמטר
keyשלsorted,maxו-min. - פונקציה שמחזירה פונקציה.
למה צריך את זה?
שתי פונקציות כמעט זהות:
def double_all(numbers):
result = []
for n in numbers:
result.append(n * 2)
return result
def square_all(numbers):
result = []
for n in numbers:
result.append(n * n)
return resultהלולאה זהה — רק הפעולה על כל איבר שונה. כל פעולה חדשה (חצי, פלוס 10, עיגול…) דורשת עוד פונקציה שלמה, וזו בדיוק הכפילות ש-DRY אוסר.
הרעיון: לכתוב את הלולאה פעם אחת, ולהעביר את הפעולה עצמה כפרמטר.
הסבר מהבסיס
פונקציה היא אובייקט
כבר ראינו בשיעור “פונקציות לעומק” שב-Python פונקציה היא אובייקט — כמו מספר או רשימה:
double(בלי סוגריים) — הפונקציה עצמה. אפשר לשמור אותה במשתנה או להעביר אותה.double(5)(עם סוגריים) — קריאה לפונקציה. התוצאה היא מה שהיא מחזירה.
Higher-Order Function
Higher-Order Function היא פונקציה ש:
- מקבלת פונקציה אחרת כפרמטר, או
- מחזירה פונקציה.
כבר השתמשתם באחת: sorted(products, key=lambda p: p[1]) — sorted מקבלת פונקציה בפרמטר key.
מכונת כביסה יודעת “לעבור על כל הבגדים”. מה לעשות לכל בגד — את זה בוחרים בתוכנית: כביסה עדינה, רגילה או ייבוש. לא קונים מכונה חדשה לכל תוכנית — מעבירים לה את התוכנית. Higher-Order Function היא המכונה, והפונקציה שמעבירים היא התוכנית.
תחביר
def apply_to_all(numbers, action): # action is a function
result = []
for n in numbers:
result.append(action(n)) # call it for each item
return result
apply_to_all([1, 2, 3], double) # pass it: no parentheses
list(map(double, [1, 2, 3])) # built-in: map
list(filter(is_even, [1, 2, 3, 4])) # built-in: filter
sorted(words, key=len) # key: sort by the result of lenדוגמת קוד ראשונה
week-2/apply_to_all.py:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))פלט:
[2, 4, 6]
[1, 4, 9]
[2, 2]פירוק הקוד שורה אחר שורה
| שורה | הסבר |
|---|---|
def apply_to_all(numbers, action): | action הוא פרמטר רגיל — רק שמצפים שיהיה בו פונקציה. |
result.append(action(n)) | קוראים לפונקציה שקיבלנו, על כל איבר. |
apply_to_all([1, 2, 3], double) | מעבירים את double בלי סוגריים — את הפונקציה, לא את התוצאה שלה. |
lambda n: n * n | פונקציה קצרה בלי שם, שנוצרת במקום. |
round | גם פונקציה מובנית אפשר להעביר. |
דוגמה נוספת
map, filter ו-key. week-2/hof_builtins.py:
prices = [100, 250, 40, 80]
with_fee = list(map(lambda p: p + 10, prices))
cheap = list(filter(lambda p: p < 100, prices))
print(with_fee)
print(cheap)
words = ["banana", "fig", "cherry", "kiwi"]
print(sorted(words, key=len))
print(max(words, key=len))
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(max(students, key=lambda s: s[1]))פלט:
[110, 260, 50, 90]
[40, 80]
['fig', 'kiwi', 'banana', 'cherry']
banana
('Dana', 92)map(func, items)— מפעילה אתfuncעל כל איבר.list(...)הופך את התוצאה לרשימה.filter(func, items)— שומרת רק את האיברים ש-funcמחזירה עליהםTrue.key=len—sortedו-maxמשווים לפי התוצאה שלlenעל כל מילה, ולא לפי המילה עצמה.
פונקציה שמחזירה פונקציה. week-2/make_multiplier.py:
def make_multiplier(factor):
def multiply(n):
return n * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))פלט:
10
15make_multiplier היא “מפעל” שמייצר פונקציות: כל קריאה מחזירה פונקציה חדשה, שזוכרת את ה-factor שלה. איך בדיוק היא זוכרת — נראה בשיעור הבא, על פונקציות מקוננות.
טעות נפוצה
הטעות: לקרוא לפונקציה במקום להעביר אותה.
apply_to_all([1, 2], double())TypeError: double() missing 1 required positional argument: 'n'למה? double() עם סוגריים קורא לפונקציה מיד — בלי פרמטר — ולכן היא נכשלת עוד לפני ש-apply_to_all מתחילה. מעבירים את הפונקציה בלי סוגריים: apply_to_all([1, 2], double). הפונקציה שמקבלת אותה היא זו שתקרא לה.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: פונקציה כפרמטר
כתבו פונקציה apply_to_all(numbers, action) שמפעילה את action על כל מספר ומחזירה רשימה חדשה. השתמשו בה עם פונקציה רגילה double, עם lambda שמעלה בריבוע, ועם round.
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))פלט צפוי:
[2, 4, 6]
[1, 4, 9]
[2, 2]הצג פתרון
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))[2, 4, 6]
[1, 4, 9]
[2, 2]מעבירים את הפונקציה בלי סוגריים (double, round). apply_to_all היא זו שקוראת לה.
תרגיל 2: map ו-filter
prices = [20, 75, 120, 45, 90]- השאירו רק מחירים מעל 50, בעזרת
filter. - הוסיפו 10 לכל מחיר שנשאר, בעזרת
map.
פלט צפוי:
[75, 120, 90]
[85, 130, 100]הצג פתרון
prices = [20, 75, 120, 45, 90]
expensive = list(filter(lambda p: p > 50, prices))
raised = list(map(lambda p: p + 10, expensive))
print(expensive)
print(raised)[75, 120, 90]
[85, 130, 100]filter שומרת רק את מה שה-lambda מחזירה עליו True. map מפעילה את ה-lambda על כל איבר. list(...) הופך את התוצאה לרשימה.
תרגיל 3: key — לפי מה למיין
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]- מיינו את המילים לפי האורך.
- מצאו את המילה הארוכה ביותר.
- מיינו את הסטודנטים לפי הציון, מהגבוה לנמוך, והדפיסו רק את השמות.
פלט צפוי:
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Aviהצג פתרון
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(sorted(words, key=len))
print(max(words, key=len))
for name, grade in sorted(students, key=lambda s: s[1], reverse=True):
print(name)['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avikey=len — משווים לפי התוצאה של len על כל מילה, לא לפי המילה עצמה.
תרגיל 4: פונקציה שמחזירה פונקציה
כתבו פונקציה make_discount(percent) שמחזירה פונקציה, שמקבלת מחיר ומחזירה אותו אחרי ההנחה.
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))פלט צפוי:
180.0
100.0הצג פתרון
def make_discount(percent):
def apply(price):
return price - price * percent / 100
return apply
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))180.0
100.0make_discount היא “מפעל” — כל קריאה מייצרת פונקציה חדשה שזוכרת את ה-percent שלה.
תרגיל 5: לקרוא במקום להעביר
def double(n):
return n * 2
print(apply_to_all([1, 2], double()))- איזו שגיאה תתקבל, ולמה?
- מה התיקון?
הצג פתרון
TypeError: double() missing 1 required positional argument: 'n'. הסוגריים ב-double()קוראים לפונקציה מיד — בלי פרמטר — עוד לפני ש-apply_to_allמתחילה.- מעבירים את הפונקציה עצמה, בלי סוגריים:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2], double))[2, 4]סיכום
- Higher-Order Function = פונקציה שמקבלת פונקציה או מחזירה פונקציה. זה אפשרי כי פונקציה היא אובייקט.
- מעבירים פונקציה בלי סוגריים — הפונקציה שמקבלת אותה היא זו שקוראת לה.
map— פעולה על כל איבר.filter— שומרת את מה שעומד בתנאי.key=ב-sorted,max,min— קובע לפי מה משווים.- פונקציה יכולה להחזיר פונקציה — “מפעל” שמייצר פונקציות מותאמות.
דוקומנטציה רשמית
- Functional Programming HOWTO — Built-in functions — map, filter ו-sorted עם פונקציה
- sorted() — key — פונקציה כפרמטר key