פונקציות – חלק 2

הקדמה

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

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

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

שימוש מתקדם בפונקציות

העברת ארגומנטים בעזרת שם

כאשר אנחנו קוראים לפונקציה, יישלחו לפי הסדר הארגומנטים שנעביר אל הפרמטרים שמוגדרים בכותרת הפונקציה.
מצב כזה נקרא positional arguments ("ארגומנטים לפי מיקום").
נסתכל על פונקציה שמקבלת טווח (סוף והתחלה, בסדר הזה) ומחזירה רשימה של כל המספרים השלמים בטווח:


In [ ]:
def my_range(end, start):
    numbers = []
    i = start
    while i < end:
        numbers.append(i)
        i += 1
    return numbers


my_range(5, 0)

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


In [ ]:
my_range(start=0, end=5)

בשורה הזו הפכנו את סדר הארגומנטים.
כיוון שבקריאה כתבנו את שמות הפרמטרים התואמים לכותרת הפונקציה, הערכים נשלחו למקום הנכון.
השיטה הזו נקראת keyword arguments ("ארגומנטים לפי שם"), ובה אנחנו מעבירים את הארגומנטים שלנו לפי שמות הפרמטרים בכותרת הפונקציה.
אנחנו משתמשים בשיטה הזו אפילו כשאנחנו לא רוצים לשנות את סדר הארגומנטים, אלא רק לעשות קצת סדר בקוד.
נבחן, לדוגמה, את המקרה של הפונקציה random.randrange – נעים יותר לראות קריאה לפונקציה עם שמות הפרמטרים:


In [ ]:
import random

In [ ]:
random.randrange(100, 200)  # מובן פחות

In [ ]:
random.randrange(start=100, stop=200)  # מובן יותר

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

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

נזכר בפונקציה get של מילון, שמאפשרת לקבל ממנו ערך לפי מפתח מסוים.
אם המפתח שאנחנו מחפשים לא קיים במילון, הפונקציה מחזירה None:


In [ ]:
ghibli_release_dates = {
    'Castle in the Sky': '1986-08-02',
    'My Neighbor Totoro': '1988-04-16',
    'Spirited Away': '2001-07-20',
    'Ponyo': '2008-07-19',
}

ponyo_release_date = ghibli_release_dates.get('Ponyo')
men_in_black_release_date = ghibli_release_dates.get('Men in Black')
print(f"Ponyo release date:        {ponyo_release_date}")
print(f"Men in Black release date: {men_in_black_release_date}")

נממש את הפונקציה get בעצמנו. לשם הנוחות, ייראה השימוש שונה במקצת:


In [ ]:
def get(dictionary, key):
    if key in dictionary:
        return dictionary[key]
    return None


ponyo_release_date = get(ghibli_release_dates, 'Ponyo')
men_in_black_release_date = get(ghibli_release_dates, 'Men in Black')
print(f"Ponyo release date:        {ponyo_release_date}")
print(f"Men in Black release date: {men_in_black_release_date}")

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


In [ ]:
ponyo_release_date = ghibli_release_dates.get('Ponyo', '???')
men_in_black_release_date = ghibli_release_dates.get('Men in Black', '???')
print(f"Ponyo release date:        {ponyo_release_date}")
print(f"Men in Black release date: {men_in_black_release_date}")

שימו לב להתנהגות המיוחדת של הפעולה get!
אם המפתח שהעברנו בארגומנט הראשון לא קיים במילון, היא מחזירה את הערך שכתוב בארגומנט השני.
אפשר להעביר לה ארגומנט אחד, ואפשר להעביר לה שני ארגומנטים. היא מתפקדת כראוי בשני המצבים.
זו לא פעם ראשונה שאנחנו רואים פונקציות כאלו. למעשה, בשבוע שעבר למדנו על פעולות builtins רבות שמתנהגות כך:
range, enumerate ו־round, כולן יודעות לקבל מספר משתנה של ארגומנטים.

נניח לפעולה get בינתיים. אל דאגה, נחזור אליה בקרוב.
בזמן שאנחנו נחים מפעולות על מילונים יום האהבה מתקרב, וחנות הוורדים הקרובה מעוניינת להעלות את מחירי כל מוצריה בשקל אחד.
התבקשנו לבנות עבורם פונקציה שמקבלת רשימת מחירים, ומחזירה רשימה שבה כל איבר גדול ב־1 מרשימת המחירים המקורית.
ניגש לעבודה:


In [ ]:
def get_new_prices(l):
    l2 = []
    for item in l:
        l2.append(item + 1)
    return l2


prices = [42, 73, 300]
print(get_new_prices(prices))

בתוך זמן קצר הפונקציה שבנינו הופכת ללהיט היסטרי בחנויות הוורדים.
מנהל קרטל הוורדים הבין־לאומי ג'וזפה ורדי יוצר איתנו קשר, ומבקש לשכלל התוכנה כך שיוכל להעלות את מחירי המוצרים כרצונו.
כדי לעמוד בדרישה, נבנה פונקציה שמקבלת רשימה, ובנוסף אליה את המחיר שיתווסף לכל איבר ברשימה זו.
כך, אם הקורא לפונקציה יעביר כארגומנט השני את הערך 2, כל איבר ברשימה יגדל ב־2.
נממש בקלילות:


In [ ]:
def get_new_prices(l, increment_by):
    l2 = []
    for item in l:
        l2.append(item + increment_by)
    return l2


prices = [42, 73, 300]
print(get_new_prices(prices, 1))
print(get_new_prices(prices, 2))

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

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


In [ ]:
def get_new_prices(l, increment_by=1):
    l2 = []
    for item in l:
        l2.append(item + increment_by)
    return l2


prices = [42, 73, 300]
print(prices)
print(get_new_prices(prices))
print(get_new_prices(prices, 5))

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

במקרה שלנו הגדרנו את הפרמטר increment_by עם ערך ברירת המחדל 1.
קריאה לפונקציה עם ארגומנט אחד בלבד (רשימת המחירים) תגדיל את כל המחירים ב־1, שהרי הוא ערך ברירת המחדל.
קריאה לפונקציה עם שני ארגומנטים (רשימת המחירים, סכום ההעלאה) תגדיל את כל המחירים בסכום ההעלאה שהועבר.

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


In [ ]:
print(get_new_prices(prices, 5))
print(get_new_prices(prices))

ממשו את פונקציית get המלאה. הפונקציה תקבל מילון, מפתח ו"ערך לשעת חירום".
החזירו את הערך השייך למפתח שהתקבל. אחרת – החזירו את הערך לשעת החירום שהועבר לפונקציה.
אם לא הועבר ערך לשעת חירום והמפתח לא נמצא במילון, החזירו None.

חשוב!
פתרו לפני שתמשיכו!

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


In [ ]:
def get_new_prices(l, increment_by=1, discount=0):
    l2 = []
    for item in l:
        new_price = item + increment_by - discount
        l2.append(new_price)
    return l2


prices = [42, 73, 300]
print(prices)
print(get_new_prices(prices, 10, 1))  # העלאה של 10, הנחה של 1
print(get_new_prices(prices, 5))  # העלאה של 5

אך מה יקרה כשנרצה לתת רק הנחה?
במקרה כזה, כשנרצה "לדלג" מעל אחד מערכי ברירת המחדל, נצטרך להעביר את שמות הפרמטרים בקריאה לפונקציה.
בדוגמה הבאה אנחנו מעלים את המחיר ב־1 (כי זו ברירת המחדל), ומורידים אותו ב־5:


In [ ]:
prices = [42, 73, 300]
print(prices)
print(get_new_prices(prices, discount=5))

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


In [ ]:
print(get_new_prices(prices, increment_by=10, discount=1))

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

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


In [ ]:
max(13, 256, 278, 887, 989, 457, 6510, 18, 865, 901, 401, 704, 640)

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


In [ ]:
def silly_function(*parameters):
    print(parameters)

    print(type(parameters))
    print('-' * 20)
    
    
silly_function('Shmulik', 'Shlomo')
silly_function('Shmulik', 1, 1, 2, 3, 5, 8, 13)
silly_function()

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

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


In [ ]:
def silly_function2(*parameters):
    print(f"Printing all the items in {parameters}:")
    for parameter in parameters:
        print(parameter)
    print("-" * 20)


silly_function2('Shmulik', 'Shlomo')
silly_function2('Shmulik', 1, 1, 2, 3, 5, 8, 13)
silly_function2()

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

חשוב!
פתרו לפני שתמשיכו!

נממש את max:


In [ ]:
def my_max(*numbers):
    if not numbers:  # אם לא סופקו ארגומנטים, אין מקסימום
        return None

    maximum = numbers[0]
    for number in numbers:
        if number > maximum:
            maximum = number
    return maximum


my_max(13, 256, 278, 887, 989, 457, 6510, 18, 865, 901, 401, 704, 640)

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


In [ ]:
def get_final_price(discount, *prices):
    return sum(prices) - discount


get_final_price(10000, 3.141, 90053)

אף שבמבט ראשון הפונקציה get_final_price עשויה להיראות מגניבה, כדאי להיזהר משימוש מוגזם בתכונה הזו של פייתון.
הדוגמה הזו אמנם מדגימה גמישות יוצאת דופן של פייתון, אבל ככלל היא דוגמה גרועה מאוד לשימוש בכוכבית.

שימו לב כמה נוח יותר להבין את המימוש הבא ל־get_final_price, וכמה נוח יותר להבין את הקריאה לפונקציה הזו:


In [ ]:
def get_final_price(prices, discount):
    return sum(prices) - discount


get_final_price(prices=(3.141, 90053), discount=10000)

תרגול ביניים: סולל דרך

כתבו פונקציה בשם create_path שיכולה לקבל מספר בלתי מוגבל של ארגומנטים.
הפרמטר הראשון יהיה אות הכונן שבו הקבצים מאוחסנים (לרוב "C"), והפרמטרים שאחריו יהיו שמות של תיקיות וקבצים.
שרשרו אותם בעזרת התו \ כדי ליצור מהם מחרוזת המייצגת נתיב במחשב. אחרי האות של הכונן שימו נקודתיים.
הניחו שהקלט שהמשתמש הכניס הוא תקין.

הנה כמה דוגמאות לקריאות לפונקציה ולערכי ההחזרה שלה:

  • הקריאה create_path("C", "Users", "Yam") תחזיר "C:\Users\Yam"
  • הקריאה create_path("C", "Users", "Yam", "HaimonLimon.mp4") תחזיר "C:\Users\Yam\HaimonLimon.mp4"
  • הקריאה create_path("D", "1337.png") תחזיר "D:\1337.png"
  • הקריאה create_path("C") תחזיר "C:"
  • הקריאה create_path() תגרום לשגיאה

מספר משתנה של ארגומנטים עם שמות

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


In [ ]:
def print_introduction(name, age):
    return f"My name is {name} and I am {age} years old."


print_introduction(age=2019, name="Gandalf")

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


In [ ]:
message = "My name is {name} and I am {age} years old"
formatted_message = message.format(name="Gandalf", age=2019)
print(formatted_message)

In [ ]:
song = "I'll {action} a story of a {animal}.\nA {animal} who's {key} is {value}."
formatted_song = song.format(action="sing", animal="duck", key="name", value="Alfred Kwak")
print(formatted_song)

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


In [ ]:
def silly_function(**kwargs):
    print(kwargs)
    print(type(kwargs))


silly_function(a=5, b=6, address="221B Baker St, London, England.")

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

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


In [ ]:
def print_sushi_recipe(**ingredients_and_amounts):
    for ingredient, amount in ingredients_and_amounts.items():
        print(f"{amount} grams of {ingredient}")


print_sushi_recipe(rice=300, water=300, vinegar=15, sugar=10, salt=3, fish=600)

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

גרמו לפונקציה print_sushi_recipe להדפיס את הרכיבים לפי סדר משקלם, מהנמוך לגבוה.

פרמטר המוגדר בעזרת שתי כוכביות תמיד יופיע בסוף רשימת הפרמטרים.

תרגול ביניים: גזור פזורפ

כתבו פונקציה בשם my_format שמקבלת מחרוזת, ומספר בלתי מוגבל של פרמטרים עם שמות.
הפונקציה תחליף כל הופעה של {key} במחרוזת, אם key הועבר כפרמטר לפונקציה.
הערך שבו {key} יוחלף הוא הערך שהועבר ל־key במסגרת העברת הארגומנטים לפונקציה.
הפונקציה לא תשתמש בפעולה format של מחרוזות או בפונקציות שלא למדנו עד כה.

הנה כמה דוגמאות לקריאות לפונקציה ולערכי ההחזרה שלה:

  • הקריאה my_format("I'm Mr. {name}, look at me!", name="Meeseeks")
    תחזיר "I'm Mr. Meeseeks, look at me!"
  • הקריאה my_format("{a} {b} {c} {c}", a="wubba", b="lubba", c="dub")
    תחזיר "wubba lubba dub dub"
  • הקריאה my_format("The universe is basically an animal", animal="Chicken")
    תחזיר "The universe is basically an animal"
  • הקריאה my_format("The universe is basically an animal")
    תחזיר "The universe is basically an animal"

חוק וסדר

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

הפונקציה תקבל:

  • את רשימת הרכיבים הקיימים בסופר ואת המחירים שלהם.
  • את רשימת הרכיבים הדרושים כדי להכין עוגה (נניח ששם כל רכיב הוא מילה בודדת).
  • אם ללקוח מגיעה הנחה.
  • שיעור ההנחה, באחוזים. כברירת מחדל, אם ללקוח מגיעה הנחה – שיעורה הוא 10%.

לצורך פישוט התרגיל, נתעלם לרגע מעניין הכמויות במתכון :)


In [ ]:
def calculate_cake_price(apply_discount, *ingredients, discount_rate=10, **prices):
    if not apply_discount:
        discount_rate = 0

    final_price = 0
    for ingredient in ingredients:
        final_price = final_price + prices.get(ingredient)
    
    final_price = final_price - (final_price * discount_rate / 100)
    return final_price


calculate_cake_price(True, 'chocolate', 'cream', chocolate=30, cream=20, water=5)

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

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

  • הארגומנטים שמיקומם קבוע ואנחנו יודעים מי הם הולכים להיות (apply_discount).
  • הארגומנטים שמיקומם קבוע ואנחנו לא יודעים מי הם הולכים להיות (ingredients).
  • הארגומנטים ששמותיהם ידועים וערך ברירת המחדל שלהם נקבע בראש הפונקציה (discount_rate).
  • ערכים נוספים ששמותיהם לא ידועים מראש (prices).

נסו לחשוב: למה נקבע דווקא הסדר הזה?

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

חשוב!
פתרו לפני שתמשיכו!

ערכי ברירת מחדל שאפשר לשנותם

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


In [ ]:
def append(item, l=[]):
    l.append(item)
    return l


print(append(4, [1, 2, 3]))
print(append('a'))

עד כאן נראה כאילו הפונקציה פועלת באופן שהיינו מצפים ממנה.
ערך ברירת המחדל של הפרמטר l הוא רשימה ריקה, ולכן בקריאה השנייה חוזרת רשימה עם איבר בודד, ['a'].

נקרא לפונקציה עוד כמה פעמים, ונגלה משהו מוזר:


In [ ]:
print(append('b'))
print(append('c'))
print(append('d'))
print(append(4, [1, 2, 3]))
print(append('e'))

משונה ולא הגיוני! ציפינו לקבל את הרשימה ['b'] ואז את הרשימה ['c'] וכן הלאה.
במקום זה בכל פעם מצטרף איבר חדש לרשימה. למה?

הסיבה לכך היא שפייתון קוראת את כותרת הפונקציה רק פעם אחת – בשלב ההגדרה של הפונקציה.
בשלב הזה שבו פייתון תקרא את כותרת הפונקציה, ערך ברירת המחדל של l יצביע לרשימה ריקה.
מאותו רגע, בכל פעם שלא נעביר ל־l ערך, l תהיה אותה רשימת ברירת מחדל שהגדרנו בהתחלה.
נדגים זאת בעזרת הדפסת ה־id של הרשימה:


In [ ]:
def view_memory_of_l(item, l=[]):
    l.append(item)
    print(f"{l} --> {id(l)}")
    return l


same_list1 = view_memory_of_l('a')
same_list2 = view_memory_of_l('b')
same_list3 = view_memory_of_l('c')
new_list1 = view_memory_of_l(4, [1, 2, 3])
new_list2 = view_memory_of_l(5, [1, 2, 3])
new_list3 = view_memory_of_l(6, [1, 2, 3])

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


In [ ]:
def append(item, l=None):
    if l == None:
        l = []

    l.append(item)
    return l


print(append(4, [1, 2, 3]))
print(append('a'))

שימו לב שהתופעה לא משתחזרת במבנים שהם immutable, כיוון שכשמם כן הם – אי אפשר לשנותם:


In [ ]:
def increment(i=0):
    i = i + 1
    return i


print(increment(100))
print(increment())
print(increment())
print(increment())
print(increment(100))

דוגמאות נוספות

חיקוי מדויק של פונקציית get למילונים:

נרענן את זיכרוננו בנוגע ל־unpacking:


In [ ]:
range_arguments = [1, 10, 3]
range_result = range(*range_arguments)
print(list(range_result))

או:


In [ ]:
preformatted_message = "My name is {me}, and my sister is {sister}"
parameters = {'me': 'Mei', 'sister': 'Satsuki'}
message = preformatted_message.format(**parameters)
print(message)

אם כך, נוכל לכתוב:


In [ ]:
def get(dictionary, *args, **kwargs):
    return dictionary.get(*args, **kwargs)

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

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

מונחים

ערכי ברירת מחדל
Default Parameters. פרמטרים שנקבע להם ערך ברירת מחדל בכותרת הפונקציה.
ארגומנטים לפי מיקום
Positional Arguments. ערכים המועברים כארגומנטים בקריאה לפונקציה לפי המיקום שלהם, וללא שם לידם.
ארגומנטים לפי שם
Keyword Arguments. ערכים המועברים כארגומנטים בקריאה לפונקציה לפי השם שלהם שנמצא לפני השווה.
מספר משתנה של ארגומנטים
נקרא לרוב *args. מאפשר לנו לקבל מספר בלתי מוגבל של ארגומנטים לפי מיקום.
מספר משתנה של ארגומנטים עם שמות
נקרא לרוב **kwargs. מאפשר לנו לקבל מספר בלתי מוגבל של ארגומנטים לפי שם.

תרגילים

כפלו לי שתו לי

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

  • הקריאה avg(5, 6) תחזיר 5.5
  • הקריאה avg(10, 5, 3) תחזיר 6
  • הקריאה avg(2) תחזיר 2
  • הקריאה avg() תחזיר None או שגיאה, לבחירתכם

Cup of join

כתבו פונקציה בשם join, שמקבלת מספר בלתי מוגבל של רשימות, כל רשימה כפרמטר.
על הפונקציה להיות מסוגלת לקבל פרמטר נוסף בשם sep.
על הפונקציה להחזיר רשימה אחת המורכבת מכלל הרשימות שהתקבלו כפרמטרים.
אם סופק הפרמטר sep, יש לשרשר אותו כאיבר בין כל שתי רשימות. אם הוא לא סופק, יש לשרשר את התו "-" במקום.

  • הקריאה join([1, 2], [8], [9, 5, 6], sep='@') תחזיר [1, 2, '@', 8, '@', 9, 5, 6]
  • הקריאה join([1, 2], [8], [9, 5, 6]) תחזיר [1, 2, '-', 8, '-', 9, 5, 6]
  • הקריאה join([1]) תחזיר [1]
  • הקריאה join() תחזיר None או שגיאה, לבחירתכם

חתכת עוגה

ממשו פונקציה בשם get_recipe_price, שלה יש:

  • פרמטר בשם prices, שיקבל מילון של מצרכים הדרושים כדי להכין מתכון מסוים.
    מפתח המילון יהיה שם המוצר, וערך המילון יהיה המחיר שלו ל־100 גרם.
    הניחו ששמו של כל רכיב הוא מילה אחת, ללא רווחים וללא תווים מיוחדים.
  • פרמטר רשות בשם optionals שיקבל רשימה של רכיבים שנתעלם מהם, משמע – לא נקנה מהם בכלל.
    אם הפרמטר לא יצוין, יש להתייחס לכל הרכיבים שהועברו.
  • עבור כל רכיב שהועבר ב־ingredients, יש להעביר ארגומנט הנושא את שמו של הרכיב.
    ערך הארגומנט צריך להיות כמות הרכיב (בגרמים) שממנה אנחנו רוצים לקנות עבור המתכון.

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

  • הקריאה get_recipe_price({'chocolate': 18, 'milk': 8}, chocolate=200, milk=100)
    תחזיר 44
  • הקריאה get_recipe_price({'chocolate': 18, 'milk': 8}, optionals=['milk'], chocolate=300)
    תחזיר 54
  • הקריאה get_recipe_price({})
    תחזיר 0