GammaCyber

Python מרוכז — פונקציות ומודולריות

תכנות, אלגוריתמיקה ו-Web · 4–5 שעות

// סקירה

Python מרוכז — פונקציות, מודולריות וטיפול בשגיאות

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

✓נושאי השיעור
  • Functions — def, return, default values, keyword args, docstrings
  • *args ו-**kwargs — פרמטרים שרירותיים, unpacking
  • Scope — LEGB rule, global, nonlocal, Closures
  • Lambda ו-Higher-Order Functions — map(), filter(), sorted(key=)
  • Exception Handling — try/except/else/finally, Custom exceptions
  • Context Managers — with statement, @contextmanager
  • Modules — import, from...import, os, json, random
  • File I/O — open, read, write, append, JSON files
  • Decorators — @decorator, wrapper pattern, functools.wraps
🎯 קשר למיון גאמא סייבר
שאלון Python: שאלות על scope, lambda, try/except — נפוצות מאוד. סדנה מעשית: OOP בנוי על פונקציות — חייב שליטה מלאה. ראיון: "הסבר לי מה זה decorator" — שאלה קלאסית. CTF: File I/O ו-exception handling בכל סקריפט ניתוח.
// פרק 1

פונקציות (Functions)

פונקציה היא גוש קוד שנותנים לו שם, שניתן להפעיל שוב ושוב. עיקרון DRY (Don't Repeat Yourself) — אם כתבת אותו קוד פעמיים, זה מועמד לפונקציה.

הגדרה בסיסית

# def = Define → הגדרת פונקציה def greet(name): # name = פרמטר (parameter) message = f"שלום, {name}!" return message # החזרת ערך result = greet("Alice") # "Alice" = ארגומנט (argument) print(result) # שלום, Alice! print(greet("Bob")) # שלום, Bob! # פונקציה ללא return — מחזירה None! def say_hi(): print("היי!") x = say_hi() # מדפיסה "היי!" print(x) # None ← כי אין return

ארגומנטים ופרמטרים

# ברירות מחדל (Default Values) def power(base, exponent=2): return base ** exponent print(power(3)) # 9 (exponent לא הועבר → 2) print(power(3, 3)) # 27 print(power(2, 10)) # 1024 # Keyword Arguments — העברה לפי שם def create_user(name, age, role="user"): return f"{name}, גיל {age}, תפקיד: {role}" print(create_user("Alice", 25)) print(create_user(age=28, name="Carol")) # סדר לא חשוב!
! ברירות מחדל Mutable — באג קלאסי ›

לעולם לא ברירת מחדל Mutable! def f(lst=[]) = באג קלאסי. הרשימה משותפת בין כל הקריאות!

# ❌ WRONG def bad(lst=[]): lst.append(1) return lst bad() # [1] bad() # [1, 1] ← משותפת! # ✅ CORRECT def good(lst=None): if lst is None: lst = [] lst.append(1) return lst
הרחבה return מרובה ו-Docstrings ›
# return מרובה — Python מחזירה tuple def stats(numbers): return sum(numbers), sum(numbers)/len(numbers), min(numbers), max(numbers) data = [85, 92, 78, 95, 88] t, a, mn, mx = stats(data) # Unpacking! print(f"סכום:{t}, ממוצע:{a:.1f}, מינ:{mn}, מקס:{mx}") # Docstrings — תיעוד הפונקציה def is_prime(n): """ בודק אם מספר שלם n הוא ראשוני. Args: n (int): המספר לבדיקה Returns: bool: True אם ראשוני, False אחרת. """ if n < 2: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True
✏תרגול עצמי3 שאלות
שאלה 1.1
מה תחזיר פונקציה שאין בה return statement?
✓ תשובה: ד) None — כל פונקציה ב-Python מחזירה ערך. אם אין return מפורש, Python מחזירה None אוטומטית.
שאלה 1.2
מה יחזיר: def f(x, y=10): return x + y; print(f(5))
✓ תשובה: ב) 15 — y לא הועבר → ברירת מחדל y=10. f(5) = 5+10 = 15.
שאלה 1.3
מה ההבדל בין פרמטר (parameter) לבין ארגומנט (argument)?
✓ תשובה: א) — def f(x): → x הוא פרמטר. f(5) → 5 הוא ארגומנט. פרמטר בהגדרה, ארגומנט בקריאה.
🎯 דגש למבחן
Mutable Default Arguments — מה קורה מאחורי הקלעים. ערכי ברירת המחדל מוערכים פעם אחת בלבד, ברגע שה-def מתבצע, ונשמרים על אובייקט הפונקציה עצמו ב-func.__defaults__. לכן def f(lst=[]) יוצר רשימה אחת שמשותפת לכל הקריאות והיא מצטברת ביניהן. הפתרון הסטנדרטי: def f(lst=None) ובתוך הגוף if lst is None: lst = [] — כך נוצרת רשימה חדשה בכל קריאה. שאלה קלאסית בראיונות ובשאלון Python.
// פרק 2

*args ו-**kwargs

*args ו-**kwargs מאפשרים לפונקציות לקבל מספר לא ידוע של ארגומנטים. זו טכניקה עוצמתית שתראה בכל ספרייה Python גדולה.

# *args — ארגומנטים positional שרירותיים def add_all(*args): print(type(args)) # <class 'tuple'> return sum(args) print(add_all(1, 2, 3)) # 6 print(add_all(10, 20, 30, 40)) # 100 # **kwargs — ארגומנטים keyword שרירותיים def show_info(**kwargs): print(type(kwargs)) # <class 'dict'> for key, val in kwargs.items(): print(f" {key} = {val}") show_info(name="Alice", age=25, city="TLV") # שילוב: positional + *args + **kwargs def full_func(first, second, *args, **kwargs): print(f"ראשון: {first}, שני: {second}") print(f"שאר positional: {args}") print(f"keyword: {kwargs}") full_func(1, 2, 3, 4, 5, x=10, y=20) # Unpacking בעת קריאה nums = [1, 2, 3] print(add_all(*nums)) # → add_all(1, 2, 3) info = {"name": "Bob", "age": 30} show_info(**info) # → show_info(name="Bob", age=30)
הרחבה Wrapper function מעשי ›
def log_and_call(func, *args, **kwargs): """קורא לפונקציה ומתעד את הקריאה""" print(f"קורא ל-{func.__name__} עם args={args}, kwargs={kwargs}") result = func(*args, **kwargs) print(f"תוצאה: {result}") return result log_and_call(pow, 2, 10) # קורא ל-pow עם args=(2, 10), kwargs={} # תוצאה: 1024 log_and_call(max, 3, 1, 7, 2) # תוצאה: 7
✏תרגול עצמי3 שאלות
שאלה 2.1
מה הטיפוס של args בתוך פונקציה שמוגדרת עם *args?
✓ תשובה: ד) tuple — *args נלכד כ-tuple (immutable). **kwargs נלכד כ-dict.
שאלה 2.2
מה יחזיר: def f(*args): return sum(args); print(f(1,2,3,4))
✓ תשובה: א) 10 — *args = tuple (1,2,3,4). sum((1,2,3,4)) = 10.
שאלה 2.3
מה יחזיר: d={}; (lambda **k: d.update(k))(a=1,b=2); print(d)
✓ תשובה: ג) {'a':1,'b':2} — **kwargs לוכד a=1, b=2 כ-dict. d.update מוסיף אותם ל-d.
// פרק 3

Scope: היכן חי משתנה

Scope (תחום) קובע היכן בקוד ניתן לגשת למשתנה. Python מחפשת משתנים לפי כלל LEGB.

אותשםתיאור
LLocalבתוך הפונקציה הנוכחית
EEnclosingפונקציה חיצונית (closure)
GGlobalברמת המודול (קובץ)
BBuilt-inPython built-ins כמו len, print
x = "global" def outer(): x = "enclosing" def inner(): x = "local" print(x) # local (L קודם) inner() print(x) # enclosing outer() print(x) # global # global keyword counter = 0 def increment(): global counter counter += 1 # nonlocal — שינוי משתנה Enclosing def make_counter(): count = 0 def add(n): nonlocal count count += n return count return add c = make_counter() print(c(5)) # 5 print(c(3)) # 8
! UnboundLocalError — הטעות הנפוצה ביותר ›
x = 10 def bad(): print(x) # ← UnboundLocalError! x += 1 # Python "יודעת" ש-x local, אבל הוא לא הוגדר לפני ההדפסה! # נכון: def good(): global x print(x) # 10 x += 1

Closure: פונקציה שזוכרת

Closure היא פונקציה פנימית שמשתמשת במשתנה מהפונקציה החיצונית, גם אחרי שהחיצונית סיימה לרוץ.

def multiplier(factor): def multiply(x): return x * factor # ← זוכרת factor! return multiply double = multiplier(2) triple = multiplier(3) print(double(5)) # 10 print(triple(5)) # 15 print(double(7)) # 14 # בדיקה: print(double.__closure__[0].cell_contents) # 2
✏תרגול עצמי3 שאלות
שאלה 3.1
מה קורה אם Python לא מוצאת משתנה בשום שלב ב-LEGB?
✓ תשובה: ב) NameError — אם Python לא מוצאת את המשתנה ב-L→E→G→B, היא זורקת NameError.
שאלה 3.2
def outer(): x = 10 def inner(): return x * 2 return inner f = outer() print(f())מה הפלט?
✓ תשובה: ד) 20 — Closure! inner זוכרת x=10 מ-outer גם אחרי שהחיצונית סיימה. f() = inner() = 10*2 = 20.
שאלה 3.3
מה ההבדל בין global לבין nonlocal?
✓ תשובה: א) — global x = x ממשתנה ברמת המודול. nonlocal x = x מהפונקציה החיצונית הקרובה (Enclosing scope).
✓סיכום ביניים — Scope ו-Closures
  • חיפוש שם מתבצע לפי סדר LEGB: Local → Enclosing → Global → Built-in.
  • global כותב למשתנה גלובלי, nonlocal כותב למשתנה של הפונקציה העוטפת.
  • Closure = פונקציה פנימית ששומרת גישה למשתני הפונקציה העוטפת גם אחרי שזו הסתיימה.
  • כל קריאה לפונקציה העוטפת יוצרת closure נפרד עם מצב משלו.
// פרק 4

Lambda ו-Higher-Order Functions

Lambda היא פונקציה קצרה בשורה אחת. Higher-Order Function = פונקציה שמקבלת פונקציה אחרת כפרמטר.

# Lambda — שורה אחת square = lambda x: x**2 print(square(5)) # 25 add = lambda x, y: x + y print(add(3, 4)) # 7 # Lambda עם תנאי classify = lambda x: "חיובי" if x > 0 else ("שלילי" if x < 0 else "אפס") print(classify(5)) # חיובי # map() — מפעיל func על כל איבר nums = [1, 2, 3, 4, 5, 6] squares = list(map(lambda x: x**2, nums)) print(squares) # [1, 4, 9, 16, 25, 36] # filter() — מסנן לפי תנאי (True = נשמר) evens = list(filter(lambda x: x % 2 == 0, nums)) print(evens) # [2, 4, 6] # sorted() עם key words = ["banana", "apple", "fig", "cherry"] by_length = sorted(words, key=lambda w: len(w)) print(by_length) # ['fig', 'apple', 'banana', 'cherry'] # מיון dict students = [ {"name": "Alice", "score": 92}, {"name": "Bob", "score": 85}, {"name": "Carol", "score": 95}, ] by_score = sorted(students, key=lambda s: s["score"], reverse=True) for s in by_score: print(f"{s['name']}: {s['score']}")
הרחבה List Comprehension vs map/filter ›
# שתי גישות זהות לאותה תוצאה: nums = range(1, 21) # גישה א: map + filter result_a = list(map(lambda x: x**2, filter(lambda x: x%2!=0, nums))) # גישה ב: List Comprehension (מועדפת ב-Python) result_b = [x**2 for x in nums if x%2!=0] # שתיהן נותנות: [1, 9, 25, 49, 81, 121, 169, 225, 289, 361] # Comprehension עדיפה: קריאה יותר, Pythonic
✏תרגול עצמי3 שאלות
שאלה 4.1
מה יחזיר: list(filter(lambda x: x>3, [1,2,3,4,5]))
✓ תשובה: ב) [4,5] — filter שומר רק ערכים שהפונקציה מחזירה True עבורם. x>3 מתקיים ל-4 ו-5 בלבד.
שאלה 4.2
מה יחזיר: list(map(str, [1,2,3]))
✓ תשובה: ג) ["1","2","3"] — map(str, ...) מפעיל str() על כל איבר: str(1)="1" וכו'.
שאלה 4.3
מה יחזיר: (lambda x,y: x**y)(2,8)
✓ תשובה: ב) 256 — Lambda שמוגדרת וקוראים לה מיד עם (2,8): 2^8 = 256.
// פרק 5

Exception Handling: טיפול בשגיאות

שגיאות (Exceptions) הן מצבים לא צפויים שקורים בזמן ריצה. try/except מאפשר "ללכוד" שגיאות ולטפל בהן במקום לקרוס.

# מבנה מלא: try / except / else / finally def safe_access(lst, index): try: value = lst[index] result = 100 / value except IndexError: print(f"אינדקס {index} מחוץ לטווח!") return None except ZeroDivisionError: print("הערך הוא 0 — אי אפשר לחלק!") return None except Exception as e: print(f"שגיאה לא צפויה: {type(e).__name__}: {e}") return None else: # מתבצע רק אם לא הייתה שגיאה! print(f"הצלחה: {result:.2f}") return result finally: # מתבצע תמיד — גם אם הייתה שגיאה! print("(ניסיון הסתיים)") safe_access([5, 0, 2], 0) # הצלחה: 20.00 safe_access([5, 0, 2], 1) # שגיאה חלוקה safe_access([5, 0, 2], 9) # אינדקס חוץ לטווח
Exceptionמתי נזרקתדוגמה
ValueErrorערך לא תקין לסוגint("abc")
TypeErrorסוג טיפוס שגוי"5" + 5
IndexErrorאינדקס מחוץ לטווח[1,2][5]
KeyErrorמפתח לא קיים ב-dict{}["key"]
ZeroDivisionErrorחלוקה באפס5/0
FileNotFoundErrorקובץ לא קייםopen("x.txt")
AttributeErrorattr לא קייםNone.split()
הרחבה Custom Exceptions — שגיאות מותאמות אישית ›
class InvalidAgeError(ValueError): """גיל לא תקין""" def __init__(self, age, message=None): self.age = age self.message = message or f"גיל {age} אינו תקין (חייב 0-150)" super().__init__(self.message) def set_age(age): if not isinstance(age, int): raise TypeError(f"גיל חייב להיות int") if age < 0 or age > 150: raise InvalidAgeError(age) return age try: set_age(-5) except InvalidAgeError as e: print(f"שגיאת גיל: {e}") print(f"הגיל שהוזן: {e.age}")
✏תרגול עצמי3 שאלות
שאלה 5.1
try: x = 1/0 except ZeroDivisionError: print("err") finally: print("done")מה הפלט?
✓ תשובה: ד) err → done — except לוכד → מדפיס "err". לאחר מכן finally תמיד מתבצע → "done".
שאלה 5.2
מה ההבדל בין except Exception as e לבין except: (ללא סוג)?
✓ תשובה: ב) — except: לוכד הכל, כולל SystemExit ו-KeyboardInterrupt. except Exception: לוכד רק exceptions "רגילים" — עדיף תמיד.
שאלה 5.3
מה תפקיד finally? מתי הוא מתבצע?
✓ תשובה: ד) — finally מתבצע תמיד: גם אם הייתה exception, גם אם לא, גם אחרי return. שימוש: cleanup, סגירת קבצים, שחרור משאבים.
✓סיכום ביניים — טיפול בשגיאות
  • try מריץ, except תופס שגיאה ספציפית, else רץ רק כשלא הייתה שגיאה, finally רץ תמיד.
  • תופסים חריגה ספציפית (ValueError) ולא except: עירום — אחרת מסתירים באגים אמיתיים.
  • with סוגר משאבים (קבצים, סוקטים) אוטומטית גם כשנזרקת שגיאה.
  • חריגות מותאמות (class MyError(Exception)) מאפשרות טיפול מדויק לפי סוג התקלה.
// פרקים 6–8

Context Managers, מודולים ו-File I/O

Context Manager מנהל "כניסה" ו"יציאה" ממשאב ומבטיח cleanup תמיד. with הוא הדרך הבטוחה לפתוח קבצים.

Context Manager — with statement

# בלי with — מסוכן! f = open("data.txt", "r") content = f.read() # מה אם תיזרק exception כאן? f.close() לא יקרא! f.close() # עם with — בטוח ומומלץ with open("data.txt", "r", encoding="utf-8") as f: content = f.read() # f.close() נקרא אוטומטית, גם אם הייתה שגיאה! # כמה context managers ביחד with open("input.txt", "r") as fin, open("output.txt", "w") as fout: for line in fin: fout.write(line.upper())

File I/O — קריאה וכתיבה

# מודים: "r"=קריאה, "w"=כתיבה (מאפס!), "a"=append, "rb"=binary # קריאה שלמה with open("file.txt", "r", encoding="utf-8") as f: content = f.read() # כל הקובץ כstring # קריאה שורה-שורה (יעיל לקבצים גדולים) with open("file.txt", "r", encoding="utf-8") as f: for line in f: # iterator — לא טוען הכל לזיכרון! print(line.strip()) # כתיבה with open("output.txt", "w", encoding="utf-8") as f: f.write("שורה ראשונה\n") # JSON import json data = {"name": "Alice", "scores": [95, 87]} with open("data.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) with open("data.json", "r", encoding="utf-8") as f: loaded = json.load(f) print(loaded["name"]) # Alice
הרחבה מודולים מובנים חשובים ›
מודולשימוש עיקרידוגמה
osפעולות על מערכת הקבציםos.path.exists("f.txt")
sysמידע על Python runtimesys.argv, sys.exit()
jsonקריאה/כתיבת JSONjson.loads("{}")
reRegular Expressionsre.search(r"\d+", text)
randomמספרים אקראייםrandom.randint(1,100)
datetimeתאריכים ושעותdatetime.now()
pathlibעבודה עם pathsPath("dir/file.txt")
import os print(os.getcwd()) # תיקייה נוכחית print(os.listdir(".")) # קבצים בתיקייה os.makedirs("new_dir", exist_ok=True) import random print(random.randint(1, 100)) # מספר שלם אקראי print(random.choice(["a","b","c"])) # בחירה אקראית lst = [1, 2, 3, 4, 5] random.shuffle(lst) # ערבוב in-place
✏תרגול עצמי3 שאלות
שאלה 6.1
מה ההבדל בין open("f.txt","w") לבין open("f.txt","a")?
✓ תשובה: א) — "w" = write: מאפס את הקובץ. "a" = append: מוסיף לסוף. שניהם יוצרים קובץ אם לא קיים.
שאלה 6.2
מה ההבדל בין json.dumps() לבין json.dump()?
✓ תשובה: ב) — dumps(data) = מחזיר JSON כ-string. dump(data, f) = כותב JSON ישירות לקובץ. s = "string".
שאלה 6.3
מה קורה ל-f בסיום ה-with: with open("a.txt","w") as f: f.write("hi")
✓ תשובה: א) — with קורא __exit__ אוטומטית בסיום הblock → f.close(). f.closed = True. זה מבטיח שהקובץ נסגר גם אם הייתה שגיאה.
// פרק 9

Decorators: מבוא

Decorator הוא פונקציה שמעטפת פונקציה אחרת ומוסיפה לה פונקציונליות — מבלי לשנות את הקוד המקורי. סינטקס: @decorator_name מעל הגדרת הפונקציה.

import time # Decorator בסיסי — מדוד זמן ריצה def timer(func): # ← מקבל פונקציה def wrapper(*args, **kwargs): # ← עוטף אותה start = time.time() result = func(*args, **kwargs) # ← מפעיל המקורית end = time.time() print(f"{func.__name__} לקח {end-start:.4f}s") return result return wrapper # ← מחזיר הגרסה העטופה @timer # שקול ל: slow_func = timer(slow_func) def slow_func(): time.sleep(0.1) return "done" slow_func() # slow_func לקח 0.1002s # Decorator שימושי — logger def log_calls(func): def wrapper(*args, **kwargs): print(f"→ {func.__name__}({args}, {kwargs})") result = func(*args, **kwargs) print(f"← {func.__name__} החזיר: {result}") return result return wrapper @log_calls def add(a, b): return a + b add(3, 4) # → add((3, 4), {}) # ← add החזיר: 7
הרחבה functools.wraps — שמירת metadata ›
from functools import wraps def my_decorator(func): @wraps(func) # ← חשוב! בלי זה func.__name__ יהיה "wrapper" def wrapper(*args, **kwargs): print("לפני") result = func(*args, **kwargs) print("אחרי") return result return wrapper @my_decorator def my_func(): """הפונקציה שלי""" return "hello" print(my_func.__name__) # my_func (לא wrapper!) print(my_func.__doc__) # הפונקציה שלי
✏תרגול עצמי3 שאלות
שאלה 9.1
כתיבת @timer מעל פונקציה שקולה ל:
✓ תשובה: ב) func = timer(func) — decorator syntax הוא syntactic sugar. @timer מעל def f(): שקול ל-f = timer(f) אחרי ההגדרה.
שאלה 9.2
מה עושה @functools.wraps(func) בתוך decorator?
✓ תשובה: א) — בלי @wraps, wrapper.__name__ = "wrapper" (לא שם המקורית). @wraps(func) מעתיק את כל ה-metadata של הפונקציה המקורית ל-wrapper.
שאלה 9.3
היכן ב-Python כנראה יש decorator? @app.route("/path")
✓ תשובה: ב) — Flask ו-web frameworks משתמשים ב-decorators להגדרת routes. @app.route("/path") הוא decorator שרושם את הפונקציה כמטפל בנתיב הזה.
🎯 דגש למבחן
Decorators בעולם התקיפה והנוזקות. דקורטור הוא בסך הכול עטיפה של פונקציה בפונקציה אחרת — f = decorator(f). בעולם ההגנה משתמשים בזה ללוגים, מדידת זמן ובדיקת הרשאות; בעולם התקיפה אותה טכניקה בדיוק (וקרובתה — Function Hooking) משמשת נוזקות כדי לעטוף פונקציות חוקיות: הקוד הזדוני רץ לפני/אחרי הפונקציה המקורית, גונב פרמטרים (סיסמאות, מפתחות) או משנה את ערך ההחזרה — והקורא לא מבחין בכלום, כי החתימה נשמרת. זו הסיבה ש-functools.wraps משמר __name__ ו-__doc__: עטיפה טובה נראית זהה למקור.
✓סיכום ביניים — Decorators
  • @decorator מעל def f שקול ל-f = decorator(f).
  • ה-wrapper מקבל *args, **kwargs כדי לעטוף כל פונקציה, ומחזיר את ערך המקור.
  • @functools.wraps(func) משמר את שם הפונקציה והתיעוד המקוריים.
  • שימושים נפוצים: לוגים, מדידת זמן, caching, אימות הרשאות.
// אתגר מעשי

אתגר מעשי — פונקציות, Closures ו-Decorators

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

T1 · מתחיל150 נק'~8 דק'debug
תיקון באג — ברירת מחדל בת-שינוי
הפונקציה אמורה להוסיף פריט ל'דלי'; אם לא סופק דלי — להתחיל רשימה חדשה בכל קריאה. אך יש בה המלכודת הקלאסית: ברירת מחדל בת-שינוי [] נוצרת פעם אחת ומשותפת בין כל הקריאות, ולכן פריטים 'זולגים' מקריאה לקריאה. תקן כך שכל קריאה ללא דלי תתחיל רשימה חדשה.
// הקוד השבור לתיקון
def add_item(item, bucket=[]): # באג כאן bucket.append(item) return bucket
דוגמאות
add_item(1) # קריאה 1 → [1]
add_item(2) # קריאה 2 (נפרדת) → [2]
add_item(3, [9]) → [9, 3]
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס תיקון באג — ברירת מחדל בת-שינוי ›
def add_item(item, bucket=None): if bucket is None: # התיקון: None במקום [] כברירת מחדל משותפת bucket = [] bucket.append(item) return bucket
T2 · בסיסי200 נק'~15 דק'algorithmic
הרכבת פונקציות (compose)
ממש compose(*funcs) שמקבל מספר פונקציות של משתנה יחיד ומחזיר פונקציה חדשה שמפעילה אותן מימין לשמאל: compose(f, g)(x) שקול ל-f(g(x)). ללא ארגומנטים — החזר פונקציית זהות (מחזירה את הקלט כמות שהוא).
דוגמאות
compose(lambda x:x+1, lambda x:x*2)(3) → 7
compose(lambda x:x*2, lambda x:x+1)(3) → 8
compose()(5) → 5
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס הרכבת פונקציות (compose) ›
def compose(*funcs): def inner(x): for f in reversed(funcs): # ימין-לשמאל: compose(f,g)(x)=f(g(x)) x = f(x) return x return inner
T3 · בינוני350 נק'~25 דק'algorithmic
דקורטור retry(times)
ממש דקורטור עם פרמטר retry(times) שעוטף פונקציה כך שאם היא זורקת חריגה — הוא ינסה שוב, עד times ניסיונות בסך הכול. אם ניסיון הצליח — החזר את ערכו. אם כל הניסיונות נכשלו — זרוק מחדש את החריגה האחרונה. שמור על הערכים המוחזרים והארגומנטים המקוריים (*a, **k).
דוגמאות
פונקציה שנכשלת פעמיים ואז מחזירה 42, עטופה ב-@retry(3) → 42
אותה פונקציה עטופה ב-@retry(2) → זורקת (רק 2 ניסיונות, צריך 3)
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס דקורטור retry(times) ›
def retry(times): def deco(fn): def wrapper(*a, **k): last = None for _ in range(times): try: return fn(*a, **k) except Exception as e: last = e raise last # כל הניסיונות נכשלו -> זרוק את האחרונה return wrapper return deco
// סיכום

סיכום השיעור

✓מה למדנו בשיעור זה
  • פונקציות: def, return, default values, keyword args, docstrings
  • *args ו-**kwargs: פרמטרים שרירותיים, unpacking
  • Scope: LEGB rule, global, nonlocal, Closures
  • Lambda ו-Higher-Order Functions: map(), filter(), sorted(key=)
  • Exception Handling: try/except/else/finally, Custom exceptions, Exception hierarchy
  • Context Managers: with statement, ביטוח cleanup
  • Modules: import, from...import, os, json, random
  • File I/O: open, read, write, append, JSON files
  • Decorators: @decorator, wrapper pattern, functools.wraps
🎯 השיעור הבא
שיעור 7 — OOP חלק א׳: Classes, Objects ו-Encapsulation
לפני שיעור 7 — ודא שאתה שולט ב-closures ו-decorators מהשיעור הזה. OOP בנוי ישירות עליהם.
✏תרגול סיום — גאמא סגנון3 שאלות
גאמא סגנון א׳
def process_data(filename, threshold=50): results = {"passed": [], "failed": [], "errors": []} try: with open(filename, "r") as f: for line in f: try: name, score = line.strip().split(",") score = int(score) if score >= threshold: results["passed"].append(name) else: results["failed"].append(name) except ValueError: results["errors"].append(line.strip()) except FileNotFoundError: return None return results מה יחזיר process_data("grades.csv", 80) אם grades.csv מכיל: "Alice,95\nBob,72\nCarol,abc\n"?
✓ תשובה: א) — Alice,95: 95≥80 → passed. Bob,72: 72<80 → failed. Carol,abc: int("abc") → ValueError → errors. הקובץ קיים → לא מחזיר None.
גאמא סגנון ב׳
def make_adder(n): def adder(x): return x + n return adder add5 = make_adder(5) add10 = make_adder(10) results = [] for val in [1, 3, 7]: results.append(add5(val)) results.append(add10(val)) print(results)מה הפלט?
✓ תשובה: ג) [6, 11, 8, 13, 12, 17] — add5(1)=6, add10(1)=11, add5(3)=8, add10(3)=13, add5(7)=12, add10(7)=17. כל closure זוכרת n שונה!
גאמא סגנון ג׳
def count_up(start=0): n = start def inc(): nonlocal n n += 1 return n return inc c = count_up(10) print(c(), c(), c())מה הפלט?
✓ תשובה: ג) 11 12 13 — closure שזוכר n=10. כל קריאה ל-c() מגדילה n ב-1 (nonlocal) ומחזירה. 10+1=11, 11+1=12, 12+1=13.
שאלות תרגול ותשובות (7)

שאלה 1 · מה מחזירה פונקציה ב-Python שאין בה פקודת return?

  • א. 0
  • ב. None
  • ג. False
  • ד. שגיאה
תשובה: ב

פונקציה ללא return מחזירה None באופן אוטומטי — זו לא שגיאה.

שאלה 2 · מה יודפס?

def f(x, y=10):
    return x + y

print(f(5))
  • א. 5
  • ב. 10
  • ג. 15
  • ד. שגיאה
תשובה: ג

y מקבל את ערך ברירת המחדל 10, ולכן 5 + 10 = 15.

שאלה 3 · מה ההבדל בין global לבין nonlocal?

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

global מפנה לתחום הגלובלי (G ב-LEGB), nonlocal לתחום העוטף (E).

שאלה 4 · מה יודפס?

def outer():
    x = 10
    def inner():
        return x * 2
    return inner()

print(outer())
  • א. 10
  • ב. 20
  • ג. None
  • ד. שגיאת NameError
תשובה: ב

inner רואה את x של outer דרך תחום ה-Enclosing, ולכן 10*2 = 20.

שאלה 5 · מדוע עדיף except ValueError על פני except: ללא סוג חריגה?

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

except עירום תופס כל חריגה כולל KeyboardInterrupt ובאגים אמיתיים — ומקשה על איתור תקלות.

שאלה 6 · מה עושה @functools.wraps(func) בתוך decorator?

  • א. משמר את __name__ ו-__doc__ של הפונקציה המקורית
  • ב. יוצר עותק גיבוי של הפונקציה
  • ג. מאיץ את הריצה באמצעות caching
  • ד. מונע שגיאות בפונקציה המעוטרת
תשובה: א

בלי wraps, שם הפונקציה והתיעוד שלה מוחלפים בשם ה-wrapper.

שאלה 7 · מה יודפס?

def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(4))
  • א. 10
  • ב. 12
  • ג. 24
  • ד. 256
תשובה: ג

4 × 3 × 2 × 1 = 24 — רקורסיה עם תנאי עצירה ב-n <= 1.