פונקציות לעומק: ערכי ברירת מחדל, Functions as Objects, ‎*args, ‎**kwargs ו-Lambda

מטרות השיעור
  • להגדיר פרמטרים עם ערך ברירת מחדל ולקרוא לפונקציה עם ארגומנטים בשם
  • להבין שפונקציה היא אובייקט — אפשר לשמור אותה במשתנה ולהעביר אותה לפונקציה אחרת
  • לכתוב פונקציות שמקבלות מספר משתנה של ארגומנטים עם *args ו-**kwargs
  • לכתוב lambda פשוטה ולהשתמש בה עם sorted(key=...)

מה נלמד?

בנושא Python מקצועי, קוד נקי ו-Debugging למדנו את הבסיס: def, פרמטרים, return. היום נלמד כמה יכולות שהופכות פונקציות לגמישות הרבה יותר:

  1. ערכי ברירת מחדל וארגומנטים בשם.
  2. Functions as Objects — פונקציה היא ערך כמו כל ערך אחר.
  3. *args — כמה ארגומנטים רגילים שרוצים.
  4. **kwargs — כמה ארגומנטים בשם שרוצים.
  5. Lambda — פונקציה קצרה בלי שם.

למה צריך את זה?

  • print("a", "b", "c", sep="-") מקבלת כמה ארגומנטים שרוצים, ועוד ארגומנט בשם. איך היא בנויה? עם *args.
  • sorted(students, key=...) מקבלת פונקציה כפרמטר, כדי לדעת לפי מה למיין.
  • FastAPI (נושא Backend ומסדי נתונים) בנוי כולו על הרעיון שפונקציה היא אובייקט: כותבים פונקציה, ו-FastAPI “רושם” אותה כ-Endpoint ומפעיל אותה כשמגיעה בקשה.

הסבר מהבסיס

ערכי ברירת מחדל (Default Values)

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

def greet(name, greeting="Hello"):
    return greeting + ", " + name

greet("Dana") → "Hello, Dana". greet("Dana", "Hi") → "Hi, Dana".

כלל: פרמטרים עם ברירת מחדל באים אחרי פרמטרים בלי.

ארגומנטים בשם (Keyword Arguments)

בקריאה אפשר לציין שם של פרמטר, והסדר כבר לא משנה:

greet(greeting="Hey", name="Avi")

זה מאוד קריא כשיש הרבה פרמטרים: create_user("dana", age=24, is_admin=False).

Functions as Objects

ב-Python, פונקציה היא אובייקט, כמו מספר או רשימה. לכן אפשר:

  • לשמור אותה במשתנה: say = greet.
  • לשים אותה ברשימה או ב-dict.
  • להעביר אותה כארגומנט לפונקציה אחרת.

ההבדל הקריטי: greet (בלי סוגריים) = הפונקציה עצמה. greet("Dana") (עם סוגריים) = הפעלה, והתוצאה שלה.

אנלוגיה: מתכון מול עוגה

greet הוא המתכון: אפשר לצלם אותו ולשלוח לחבר. greet("Dana") הוא העוגה: התוצאה של הכנה לפי המתכון. כשמעבירים פונקציה לפונקציה אחרת, שולחים את המתכון, כדי שהיא תכין לפיו מתי שהיא צריכה.

*args — מספר משתנה של ארגומנטים

כוכבית לפני שם הפרמטר אומרת: “אסוף את כל הארגומנטים הרגילים (לפי מיקום) לתוך tuple”:

def total(*numbers):
    return sum(numbers)

total(1, 2)          # numbers = (1, 2)
total(5, 10, 20, 1)  # numbers = (5, 10, 20, 1)

השם args הוא מוסכמה. הכוכבית היא שעושה את העבודה.

**kwargs — מספר משתנה של ארגומנטים בשם

שתי כוכביות: “אסוף את כל הארגומנטים בשם לתוך dict”:

def describe(**details):
    for key, value in details.items():
        print(key, "=", value)

describe(name="Dana", age=24, city="Haifa")
# details = {"name": "Dana", "age": 24, "city": "Haifa"}

kwargs = keyword arguments.

Lambda

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

lambda x: x * 2

שקול ל:

def double(x):
    return x * 2
  • אחרי lambda: הפרמטרים. אחרי :: ביטוי אחד שהוא הערך המוחזר (בלי return).
  • השימוש העיקרי: להעביר פונקציה קצרה כארגומנט, במקום להגדיר def נפרד. בעיקר עם sorted, min, max.

תחביר

# default value + keyword arguments
def func(required, optional="default"):
    ...
func("a")
func("a", optional="b")

# function as an object
f = func            # no parentheses: the function itself
f("a")              # call it through the new name

def apply(operation, value):
    return operation(value)    # call the function we received

# *args and **kwargs
def func(*args, **kwargs):
    # args   -> tuple of positional arguments
    # kwargs -> dict of keyword arguments
    ...

# lambda
lambda parameters: expression
sorted(items, key=lambda item: item["price"])

דוגמת קוד ראשונה

week-2/functions_deep.py:

def make_greeting(name, greeting="Hello", punctuation="!"):
    return greeting + ", " + name + punctuation


print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punctuation="?"))


def shout(text):
    return text.upper()


def whisper(text):
    return text.lower() + "..."


def say(message, style):
    return style(message)


print(say("Hello there", shout))
print(say("Hello there", whisper))

פלט:

Hello, Dana!
Good morning, Avi!
Hello, Noa?
HELLO THERE
hello there...

פירוק הקוד שורה אחר שורה

שורההסבר
def make_greeting(name, greeting="Hello", punctuation="!")name חובה. השניים האחרים אופציונליים, עם ברירות מחדל.
make_greeting("Dana")שני האופציונליים לוקחים ברירת מחדל.
make_greeting("Avi", "Good morning")ארגומנט שני לפי מיקום נכנס ל-greeting.
make_greeting("Noa", punctuation="?")ארגומנט בשם: מדלגים על greeting (ברירת מחדל) וקובעים רק את punctuation.
def say(message, style):style הוא פרמטר שמצפה לפונקציה.
return style(message)מפעילים את הפונקציה שהתקבלה. say לא יודעת ולא אכפת לה איזו פונקציה זו.
say("Hello there", shout)מעבירים את shout בלי סוגריים, כלומר את הפונקציה עצמה.

דוגמה נוספת

*args, **kwargs ו-lambda במערכת אמיתית קטנה. week-2/store_tools.py:

def order_total(*prices, discount=0):
    subtotal = sum(prices)
    return subtotal - subtotal * discount / 100


print(order_total(10, 20, 30))
print(order_total(100, 50, discount=10))


def build_user(username, **extra):
    user = {"username": username}
    for key, value in extra.items():
        user[key] = value
    return user


print(build_user("dana"))
print(build_user("avi", age=30, city="Haifa", is_admin=True))


products = [
    {"name": "Laptop", "price": 3500},
    {"name": "Mouse", "price": 80},
    {"name": "Desk", "price": 900},
]

by_price = sorted(products, key=lambda product: product["price"])
for product in by_price:
    print(product["name"], product["price"])

cheapest = min(products, key=lambda product: product["price"])
print("Cheapest:", cheapest["name"])

פלט:

60.0
135.0
{'username': 'dana'}
{'username': 'avi', 'age': 30, 'city': 'Haifa', 'is_admin': True}
Mouse 80
Desk 900
Laptop 3500
Cheapest: Mouse

פירוק:

  • def order_total(*prices, discount=0): — *prices אוסף כל מספר של מחירים ל-tuple. discount מופיע אחרי *prices, ולכן אפשר להעביר אותו רק בשם (discount=10). אחרת, Python לא תדע איפה נגמרים המחירים.
  • build_user("avi", age=30, ...) — username מקבל "avi", וכל השאר נאסף ל-extra כ-dict: {"age": 30, "city": "Haifa", "is_admin": True}.
  • sorted(products, key=lambda product: product["price"]) — sorted לא יודעת איך להשוות בין dicts. ה-key הוא פונקציה שאומרת לה: “לכל מוצר, תשתמשי במחיר שלו כדי להשוות”. ה-lambda נקראת פעם אחת לכל מוצר.
  • min(..., key=...) — אותו רעיון: “המינימלי לפי מחיר”. מחזיר את המוצר כולו, ולא רק את המחיר.

טעות נפוצה

הטעות 1: להעביר תוצאה במקום פונקציה.

print(say("Hello", shout()))
TypeError: shout() missing 1 required positional argument: 'text'

shout() עם סוגריים מנסה להפעיל את shout מיד, בלי ארגומנט. כשמעבירים פונקציה, כותבים את השם בלבד: shout.

הטעות 2: רשימה או dict כערך ברירת מחדל.

def add_item(item, cart=[]):
    cart.append(item)
    return cart


print(add_item("milk"))
print(add_item("bread"))
['milk']
['milk', 'bread']

ציפינו ל-['bread'] בקריאה השנייה! ערך ברירת המחדל [] נוצר פעם אחת, כשהפונקציה מוגדרת, וכל הקריאות חולקות את אותה רשימה (Mutable, השיעור הקודם!). התיקון המקובל:

def add_item(item, cart=None):
    if cart is None:
        cart = []
    cart.append(item)
    return cart

הטעות 3: lambda מסובכת מדי.

result = sorted(users, key=lambda u: (0 if u["active"] else 1, -u["score"], u["name"].lower()))

זה עובד, אבל אף אחד לא יבין את זה בעוד שבוע (KISS!). אם lambda לא קריאה במבט, כותבים def עם שם ברור.

הטעות 4: פרמטר עם ברירת מחדל לפני פרמטר חובה.

def greet(greeting="Hello", name):
SyntaxError: parameter without a default follows parameter with a default

תרגול

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

תרגיל 1: ברירת מחדל וארגומנטים בשם

כתבו פונקציה make_greeting(name, greeting="Hello", punct="!").

print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punct="?"))

פלט צפוי:

Hello, Dana!
Good morning, Avi!
Hello, Noa?
הצג פתרון
def make_greeting(name, greeting="Hello", punct="!"):
    return greeting + ", " + name + punct


print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punct="?"))
Hello, Dana!
Good morning, Avi!
Hello, Noa?

בקריאה השלישית punct="?" עובר לפי שם, ולכן אפשר לדלג על greeting ולהשאיר לו את ברירת המחדל.

תרגיל 2: *args

כתבו פונקציה average(*numbers) שמקבלת כל כמות מספרים ומחזירה את הממוצע. אם לא הועבר אף מספר — היא זורקת ValueError עם ההודעה שבפלט הצפוי.

print(average(80, 90))
print(average(70, 80, 90, 100))
try:
    average()
except ValueError as error:
    print("error:", error)

פלט צפוי:

85.0
85.0
error: need at least one number
הצג פתרון
def average(*numbers):
    if len(numbers) == 0:
        raise ValueError("need at least one number")
    return sum(numbers) / len(numbers)


print(average(80, 90))
print(average(70, 80, 90, 100))
try:
    average()
except ValueError as error:
    print("error:", error)
85.0
85.0
error: need at least one number

*numbers אוסף את כל הארגומנטים לפי מיקום ל-tuple.

תרגיל 3: **kwargs

כתבו פונקציה format_profile(name, **details) שמחזירה שורה עם השם, ואחריו כל פרט נוסף בפורמט key=value.

print(format_profile("Dana", age=24, city="Haifa"))
print(format_profile("Avi"))

פלט צפוי:

Dana: age=24 city=Haifa
Avi:
הצג פתרון
def format_profile(name, **details):
    line = name + ":"
    for key, value in details.items():
        line = line + " " + key + "=" + str(value)
    return line


print(format_profile("Dana", age=24, city="Haifa"))
print(format_profile("Avi"))
Dana: age=24 city=Haifa
Avi:

**details אוסף את כל הארגומנטים בשם ל-dict.

תרגיל 4: lambda במיון

products = [("Laptop", 3500), ("Mouse", 80), ("Desk", 900)]
  1. מיינו לפי המחיר, מהזול ליקר, והדפיסו כל מוצר.
  2. הדפיסו את שם המוצר היקר ביותר.

פלט צפוי:

Mouse 80
Desk 900
Laptop 3500
Most expensive: Laptop
הצג פתרון
products = [("Laptop", 3500), ("Mouse", 80), ("Desk", 900)]

for name, price in sorted(products, key=lambda p: p[1]):
    print(name, price)

most_expensive = max(products, key=lambda p: p[1])
print("Most expensive:", most_expensive[0])
Mouse 80
Desk 900
Laptop 3500
Most expensive: Laptop

lambda p: p[1] היא פונקציה קצרה שמחזירה את המחיר — sorted ו-max משווים לפיה.

תרגיל 5: ברירת מחדל משתנה

def add_item(item, cart=[]):
    cart.append(item)
    return cart

print(add_item("milk"))
print(add_item("bread"))
  1. מה יודפס בקריאה השנייה, ולמה?
  2. תקנו את הפונקציה.
הצג פתרון
  1. יודפס ['milk', 'bread']. ברירת המחדל [] נוצרת פעם אחת, כשהפונקציה מוגדרת — וכל הקריאות חולקות את אותה רשימה.
  2. משתמשים ב-None, ויוצרים רשימה חדשה בתוך הפונקציה:
def add_item(item, cart=None):
    if cart is None:
        cart = []
    cart.append(item)
    return cart


print(add_item("milk"))
print(add_item("bread"))
['milk']
['bread']

סיכום

  • ברירת מחדל: def f(a, b=10). פרמטרים עם ברירת מחדל באים אחרונים. לעולם לא [] או {} כברירת מחדל (משתמשים ב-None).
  • ארגומנט בשם: f(a=1, b=2). קריא, והסדר לא משנה.
  • פונקציה היא אובייקט: f = הפונקציה, f() = הפעלה. אפשר לשמור אותה במשתנה, ב-dict, ולהעביר כארגומנט.
  • *args אוסף ארגומנטים רגילים ל-tuple. **kwargs אוסף ארגומנטים בשם ל-dict.
  • lambda x: expression היא פונקציה קצרה בלי שם. מושלמת ל-key= של sorted/min/max. אם היא מסובכת, עדיף def.

דוקומנטציה רשמית