פעולות (פונקציות)
ככל שתוכנית גדלה, קשה יותר לקרוא אותה: עשרות שורות בגוש אחד, אותו חישוב כתוב ביותר ממקום אחד, ועל כל תיקון צריך לזכור לחזור. בפרק הזה נלמד לתת לכל משימה שם, לכתוב אותה פעם אחת ולזמן אותה בכל פעם שנזדקק לה. זו הפעולה, ועליה בנויות ארבע שאלות הבגרות שנפתור בסוף הפרק.
- נחלק משימה גדולה לפעולות קטנות, ונגדיר את תפקיד כל פעולה במשפט אחד.
- נכתוב פעולות שמקבלות פרמטרים, חלקם עם ערך ברירת מחדל.
- נבחין בין פעולה שמחזירה ערך לפעולה שאינה מחזירה ערך.
- נעקוב אחר הזימון והחזרה בטבלת מעקב עם משתנים מקומיים.
- נעביר רשימות לפעולות ונחזיר אותן מהן, ונדע מתי הרשימה המקורית משתנה.
- נכיר את טווח ההכרה של כל משתנה, ונבנה תוכנית שלמה סביב הפעולה
main.
7.1 פעולות ככלי לעידון וחלוקת משימות
בודקים את הזרם בשני נגדים על שולחן המעבדה, וכותבים לכל אחד מהם את אותן השורות:
v = 12
r = 480
print("V =", v, " R =", r)
print("I =", v / r, "A")
v = 5
r = 250
print("V =", v, " R =", r)
print("I =", v / r, "A")V = 12 R = 480 I = 0.025 A V = 5 R = 250 I = 0.02 A
ההבדל בין שני הקטעים הוא רק בערכים. אם נרצה להציג את הזרם ביחידה אחרת, נצטרך לשנות כל קטע, ואולי נשכח אחד מהם. לכן נכתוב את הקטע פעם אחת וניתן לו שם:
def show_current(v, r):
print("V =", v, " R =", r)
print("I =", v / r, "A")
show_current(12, 480)
show_current(5, 250)V = 12 R = 480 I = 0.025 A V = 5 R = 250 I = 0.02 A
הפלט זהה לחלוטין. נקרא את ההגדרה משמאל:
- המילה השמורה
defמודיעה שכאן מוגדרת פעולה, ו-show_currentהוא שמה, לפי כללי השמות של משתנים. (v, r)הם שני הפרמטרים: הערכים שהפעולה צריכה. הסוגריים הכרחיים גם כשאין ביניהם דבר, ואחריהם נקודתיים.- השורות המוזחות מתחת לשורה הראשונה הן גוף הפעולה, כמו גוף התנאי וגוף הלולאה.
- השורה
show_current(12, 480)היא הזימון: רק כאן הגוף מתבצע.
הפעולות המוכנות שאנחנו מכירים, כמו len ו-round, נכתבו בידי מתכנתי פייתון. אבל התועלת הגדולה אינה במניעת חזרות, אלא בדרך החשיבה. עידון פירושו שמסתכלים על המשימה הגדולה, מחלקים אותה למשימות קטנות יותר, וממשיכים לחלק עד שכל משימה קטנה מספיק כדי שנדע איך לכתוב אותה.
את חישוב הסטייה נכתוב בסעיף 7.4, את הפרדת הנפסלים בסעיף 7.8, ואת התוכנית כולה נרכיב בסעיף 7.11.
def beep(): נכונה, וההגדרה def beep: היא שגיאת תחביר.7.2 הגדרת תפקיד הפעולות
לפני שכותבים את הגוף מגדירים את תפקיד הפעולה, בתשובה על ארבע שאלות: מה שמה? מה היא מקבלת? מה היא נותנת? ומה היא עושה, במשפט אחד? ננסה זאת על פעולות מוכרות, ובשורה האחרונה על פעולה שעוד נכתוב:
| הפעולה | מקבלת | נותנת | תפקידה |
|---|---|---|---|
len(s) | מחרוזת או רשימה | מחזירה מספר שלם | מספר התווים או האיברים |
print(a, b) | ערכים מכל טיפוס | מדפיסה ואינה מחזירה דבר | הצגת הערכים על המסך |
deviation(n, m) | שני מספרים | מחזירה מספר | סטיית המדידה מן הערך הנקוב באחוזים |
- תפקיד אחד לכל פעולה. אם בתיאור שלה נזקקים למילה "ואז", אולי אלה שתי פעולות.
- השם אומר את התפקיד:
read_batchקוראת, ו-is_okשואלת. שם ברור חוסך הסבר. - פעולה שמחשבת אינה קוראת ואינה מדפיסה: היא מקבלת פרמטרים ומחזירה תוצאה. אילו קראה את המדידה בעצמה, לא היינו יכולים לחשב בעזרתה מאה מדידות השמורות ברשימה.
אפשר לכתוב את התפקיד בשורה הראשונה של הגוף בין מירכאות משולשות. זו מחרוזת תיעוד, והיא אינה משפיעה על הביצוע. נראה אחת בפעולה deviation בסעיף 7.4.
ההגדרה אינה מבצעת דבר. כשהתוכנית מגיעה ל-def היא שומרת את הפעולה בשמה ומדלגת על גופה. הגוף מתבצע רק בזימון, ובכל זימון מחדש. לכן בשורת ההגדרה לא הודפס דבר, והמילה beep הודפסה רק בשני הזימונים:
print("start")
def beep():
print("beep")
beep()
print("end")
beep()start beep end beep
def max(a, b): אי אפשר להשתמש ב-max המוכנה בכל התוכנית. לכן בוחרים שם אחר.mystery.def mystery(bits, n):
k = 0
for b in bits:
if b == 1:
k += 1
print(k >= n)
mystery([1, 0, 1, 1], 3)
mystery([1, 0, 0, 1], 3)7.3 פעולות המקבלות פרמטרים, כולל פרמטרים עם ערך ברירת מחדל
def show_current(v, r):
print("I =", round(v / r, 3), "A")
show_current(12, 480)
show_current(480, 12)
show_current(r=480, v=12)I = 0.025 A I = 40.0 A I = 0.025 A
=. אם הפרמטר לא צוין בזימון הוא מקבל אותו, ואם צוין, הערך החדש בא במקומו. אנחנו כבר מכירים זאת: print מדפיסה עם רווח מפריד כל עוד לא קבענו sep.def led_series(vs, vf=2.0, i=0.02):
r = (vs - vf) / i
print("R =", round(r), "ohm")
led_series(12)
led_series(5, 3.2)
led_series(5, 3.2, 0.01)
led_series(5, i=0.01)R = 500 ohm R = 90 ohm R = 180 ohm R = 300 ohm
בזימון האחרון דילגנו על vf בכך שציינו את השם i.
def led_series(vf=2.0, vs): היא שגיאת תחביר: פייתון לא תדע למי לתת את הערך היחיד ב-led_series(12).def show(x, k=2, d=0):
print(x * k + d)
show(3)
show(3, 4)
show(3, d=1)
show(3, 4, 1)7.4 פעולות המחזירות ערך
return. כשהביצוע מגיע לפקודה הזו הפעולה מסתיימת מיד, ומה שאחריה אינו מתבצע.נכתוב את חוק אום כפעולה שמחזירה את הזרם. הזימון שווה לערך המוחזר, ולכן אפשר להציב אותו במשתנה, לכתוב אותו בתוך ביטוי או להעביר אותו ל-print:
def current(v, r):
return v / r
i = current(12, 480)
print(i)
print(current(9, 300) * 1000, "mA")
print(current(5, 250) + current(12, 400))0.025 30.0 mA 0.05
print במקום return, רואים את המספר על המסך וחושבים שהפעולה עובדת. אבל היא אינה מחזירה דבר, ולכן i מקבל את הערך None, והתוכנית נעצרת בחישוב הראשון. print מיועדת לאדם שמסתכל על המסך, ו-return לשאר התוכנית:def current(v, r):
print(v / r)
i = current(12, 480)
print(i * 1000)0.025 TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
בפעולה יכולה להיות יותר מפקודת return אחת, ורק אחת מהן מתבצעת. מכיוון ש-return מסיימת את הפעולה, אין צורך ב-else אחריה, כמו שנראה בסעיפים הבאים.
def deviation(nominal, measured):
"""Deviation from the nominal value, in percent."""
return abs(measured - nominal) * 100 / nominal
def is_ok(nominal, measured, tol=5):
return deviation(nominal, measured) <= tol
print(deviation(1000, 1030))
print(is_ok(1000, 1030))
print(is_ok(1000, 1080))
print(is_ok(1000, 1080, 10))
if not is_ok(470, 500):
print("Reject")3.0 True False True Reject
הפעולה is_ok מחזירה True או False, ולכן כותבים את הזימון שלה ישירות בתנאי. אין צורך ב-if בתוכה, כי ההשוואה עצמה נותנת ערך בוליאני.
אם רוצים להחזיר יותר מערך אחד, כותבים ביניהם פסיק. הערכים מגיעים בטאפל אחד, ואפשר לפזר אותם למשתנים, כמו ב-v, i = 12, 0.5 מסעיף 2.6. נחשב למשל את המתח הגבוה ביותר ואת הזרם הגבוה ביותר שנגד בהספק נתון עומד בהם:
def limits(r, p):
v = (p * r) ** 0.5
i = (p / r) ** 0.5
return round(v, 1), round(i, 3)
volts, amps = limits(470, 0.25)
print(volts, "V")
print(amps, "A")
print(limits(100, 1))10.8 V 0.023 A (10.0, 0.1)
return מחזיר None בלי שום הודעה. אם כותבים return בתוך if ולא כותבים דבר למקרה האחר, הפעולה תחזיר None במקרה ההוא.def parallel(r1, r2):
return r1 * r2 / (r1 + r2)
print(parallel(100, 300))
print(parallel(parallel(100, 300), 75))7.5 פעולות שאינן מחזירות ערך
יש פעולות שתפקידן לעשות משהו ולא לחשב: להדפיס טבלה או להפעיל מכשיר. אין בהן return, או שיש בהן return בלי ערך. אז הזימון הוא פקודה בפני עצמה ולא חלק מביטוי. מה שחוזר מהן הוא הערך המיוחד None, שפירושו "כלום":
def rule(n, ch="-"):
print(ch * n)
rule(12)
rule(6, "=")
x = rule(3)
print(x)------------ ====== --- None
return בלי ערך מסיימת את הפעולה באמצעה, וזה שימושי כשאי אפשר להמשיך. נציג את הזרם, וכשההתנגדות אפס לא נחלק בה.def show_current(v, r):
if r == 0:
print("Short circuit")
return
print("I =", v / r, "A")
show_current(12, 0)
show_current(12, 480)Short circuit I = 0.025 A
print(rule(4)), מופיע קודם מה שהיא מדפיסה, ואחר כך המילה None. אם מחשבים איתו, התוכנית נעצרת. זה מה שראינו ב-r = r.sort() בסעיף 6.10: פעולה שמשנה את הרשימה אינה מחזירה דבר.def mark(v, top=5):
if v > top:
print(v, "high")
return
print(v, "ok")
mark(3)
mark(7)
mark(7, 8)
print(mark(9))7.6 זימון פעולות
הזימון מקפיץ את הביצוע אל הפעולה, ואחר כך מחזיר אותו בדיוק למקום שממנו יצא. נעקוב אחר הדרך צעד אחר צעד:
פעולה מזמנת פעולה. זו החלוקה שדיברנו עליה, וראינו אותה בסעיף 7.4: is_ok מזמנת את deviation, והחזרה מגיעה תמיד למי שזימן.
def to_base(value, prefix):
if prefix == "k":
return value * 1000
if prefix == "m":
return value / 1000
return value
print(12 / to_base(4.8, "k"))
print(to_base(250, "m"))
print(round(to_base(9, "") / to_base(4.7, "k") * 1000, 2), "mA")0.0025 0.25 1.91 mA
הסדר. התוכנית נקראת מלמעלה למטה, ולכן ההגדרה צריכה לעבור לפני שהזימון מתבצע, אחרת התוכנית נעצרת בשגיאה NameError. אבל בתוך גוף של פעולה מחפשים את השם רק בזמן הריצה, ולכן מותר לפעולה לזמן פעולה שכתובה אחריה, כל עוד הזימון הראשון בא אחרי שתי ההגדרות.
print(current) נותנת את הפעולה עצמה כערך ואינה מפעילה אותה, ולכן מודפסים תיאורה וכתובתה בזיכרון: <function current at 0x000001F2A4C8>def twice(x):
print("twice", x)
return 2 * x
def plus(a, b):
print("plus", a, b)
return a + b
print(plus(twice(1), twice(3)))7.7 טבלת מעקב
נרחיב את טבלת המעקב מסעיף 2.16 בשני סוגי עמודות: עמודה לכל משתנה מקומי, שמעליה כותבים את שם הפעולה, ועמודה לערך המוחזר, שמעליה כותבים power().
def power(v, i):
p = v * i
return p
a = power(12, 2)
b = power(5, 3)
print(a + b)| פקודה | a | b | power.v | power.i | power.p | power() | פלט |
|---|---|---|---|---|---|---|---|
| a = power(12, 2) | |||||||
| p = v * i | 12 | 2 | 24 | ||||
| return p | 24 | 24 | |||||
| b = power(5, 3) | |||||||
| p = v * i | 5 | 3 | 15 | ||||
| return p | 15 | 15 | |||||
| print(a + b) | 39 |
- העמודות שמתחילות ב-
power.הן המשתנים של הפעולה, וביניהם שני הפרמטרים. ערכי הפרמטרים מופיעים בשורה הראשונה של הגוף, כפי שהם אחרי ביצוע אותה שורה. - שורת הזימון
a = power(12, 2)מתחילה ריקה, כי היא עוד לא הסתיימה. את הערך שלaרושמים בטבלה בשורהreturn p, שם הערך חוזר, ובאותה שורה רושמים גם את הפלט כשהזימון כתוב בתוךprint. - בכל זימון המשתנים של הפעולה נולדים מחדש, ואין להם קשר לזימון הקודם.
return שונה.def limit(v, top=5):
if v > top:
return top
return v
x = limit(7)
y = limit(3)
print(x, y)| פקודה | x | y | limit.v | limit.top | limit() | v > top | פלט |
|---|---|---|---|---|---|---|---|
| x = limit(7) | |||||||
| if v > top: | 7 | 5 | True | ||||
| return top | 5 | 5 | |||||
| y = limit(3) | |||||||
| if v > top: | 3 | 5 | False | ||||
| return v | 3 | 3 | |||||
| print(x, y) | 5 3 |
def fix(a, b=1):
c = a + b
if c > 5:
c = c - 5
return c
m = fix(3, 4)
print(m)7.8 פעולות המקבלות מבני נתונים, פעולות המחזירות מבני נתונים
פרמטר יכול להיות רשימה או טאפל, וכך כותבים את הפעולה פעם אחת והיא עובדת על כל אורך. פעולה יכולה גם להחזיר רשימה: מתחילים ברשימה ריקה, מוסיפים לה בלולאה, ומחזירים אותה בסוף. זו הפרדת הרכיבים בארגז הנגדים:
def deviation(nominal, measured):
return abs(measured - nominal) * 100 / nominal
def good_parts(nominal, batch, tol=5):
good = []
for m in batch:
if deviation(nominal, m) <= tol:
good.append(m)
return good
batch = [1020, 940, 1049, 1100]
print(good_parts(1000, batch))
print(good_parts(1000, batch, 10))
print(len(good_parts(1000, batch, 1)))
print(batch)[1020, 1049] [1020, 940, 1049, 1100] 0 [1020, 940, 1049, 1100]
led_series כך שתחזיר את הערך במקום להדפיס אותו, ופעולה שנייה שתבחר מסדרת הערכים התקניים את הקרוב ביותר.def led_series(vs, vf=2.0, i=0.02):
return (vs - vf) / i
def nearest(values, target):
best = values[0]
for v in values:
if abs(v - target) < abs(best - target):
best = v
return best
e12 = [100, 120, 150, 180, 220, 270,
330, 390, 470, 560, 680, 820]
needed = led_series(12)
print(needed)
print(nearest(e12, needed))
print(nearest(e12, led_series(9)))500.0 470 330
זה החיפוש אחר ערך קיצון מסעיף 6.12, אבל הקריטריון כאן הוא המרחק מן היעד.
מה קורה לרשימה בתוך הפעולה?:
הפרמטר מצביע על אותה רשימה שהעברנו, ולא על עותק שלה, כמו שראינו ב-b = a בסעיף 6.4. לכן zero_negatives משנה את הרשימה המקורית, לעומת good_parts, שהשאירה אותה כמו שהיא והחזירה רשימה חדשה. בוחרים לפי התפקיד, ואומרים זאת בשם הפעולה:
def zero_negatives(values):
for i in range(len(values)):
if values[i] < 0:
values[i] = 0
a = [3, -1, 2, -5]
zero_negatives(a)
print(a)[3, 0, 2, 0]
x = 0 לא שינתה את n, השורה items[0] = 0 שינתה את הרשימה המקורית, והשורה items = [] גרמה לפרמטר להצביע על רשימה אחרת ולא שינתה דבר בחוץ:def reset(x, items):
x = 0
items[0] = 0
items = []
n = 5
b = [5, 6]
reset(n, b)
print(n, b)5 [0, 6]
def pick(values, k):
result = []
for i in range(0, len(values), k):
result.append(values[i])
return result
def bump(values):
values[0] += 1
a = [1, 2, 3, 4, 5]
b = pick(a, 2)
bump(b)
print(a)
print(b)7.9 שימוש באופרטור * כדי לארוז ולפרק פרמטרים (Packing and Unpacking)
הפעולה print מקבלת כל מספר של ערכים. בעזרת האופרטור * גם אנחנו נכתוב פעולות כאלה, ויש לו שני תפקידים לפי מקומו:
| איפה כותבים אותו | דוגמה | מה הוא עושה |
|---|---|---|
| בהגדרה, לפני פרמטר | def series(*resistors): | אריזה: אוסף את כל הערכים לטאפל אחד |
| בזימון, לפני רשימה | series(*parts) | פירוק: מחלק את האיברים לערכים נפרדים |
ההתנגדות השקולה של נגדים בטור היא סכומם, ומספרם משתנה ממעגל למעגל:
def series(*resistors):
print(resistors)
return sum(resistors)
print(series(100, 220))
print(series(100, 220, 470))
print(series())(100, 220) 320 (100, 220, 470) 790 () 0
בתוך הפעולה resistors הוא טאפל רגיל, ויכול להיות גם ריק. לפניו יכולים לבוא פרמטרים רגילים שמקבלים את הערכים הראשונים, וכל מה שנשאר נכנס לטאפל, כמו ב-def report(name, *values):.
פירוק הוא הכיוון ההפוך: יש רשימה, והפעולה רוצה ערכים נפרדים. current(*pair) נותן את מה שנותן current(pair[0], pair[1]) בלי לספור אינדקסים, ומספר האיברים חייב להיות שווה למספר הפרמטרים. עם print מודפסים איברי הרשימה בלי סוגריים:
def current(v, r):
return v / r
def series(*resistors):
return sum(resistors)
pair = [12, 480]
print(current(pair[0], pair[1]))
print(current(*pair))
parts = [100, 220, 470]
print(series(*parts))
print(parts)
print(*parts)
print(*parts, sep=" + ")0.025 0.025 790 [100, 220, 470] 100 220 470 100 + 220 + 470
series(parts). אז הרשימה כולה נכנסת כערך אחד לטאפל, ו-sum נעצרת, כי אי אפשר לחבר רשימה עם מספר.def top(limit, *values):
n = 0
for v in values:
if v > limit:
n += 1
return n
data = [4, 9, 6]
print(top(5, 4, 9, 6))
print(top(5, *data))
print(top(*data))
print(top(5))7.10 טווח ההכרה של משתנים (שימוש ב-global) (global)
gap בפעולה הראשונה ו-gap בשנייה הם שני משתנים שונים, ושתיהן יכולות לקרוא את limitמשתנה מקומי נעלם כשהפעולה מסתיימת, ואם מדפיסים אותו אחריה התוכנית נעצרת בשגיאה NameError. אותו שם בתוך הפעולה ומחוצה לה הוא שני משתנים נפרדים. נראה זאת בטבלת המעקב: העמודה current.i משתנה, והעמודה i אינה זזה:
i = 99
def current(v, r):
i = v / r
return i
a = current(12, 480)
print(a, i)| פקודה | i | a | current.i | current() | פלט |
|---|---|---|---|---|---|
| i = 99 | 99 | ||||
| a = current(12, 480) | |||||
| i = v / r | 0.025 | ||||
| return i | 0.025 | 0.025 | |||
| print(a, i) | 0.025 99 |
energy, עדיין אין לו ערך, והתוכנית נעצרת בשגיאה UnboundLocalError:energy = 0
def add_load(watts, hours):
energy = energy + watts * hours
add_load(60, 2)הפקודה global אומרת: השם הזה הוא המשתנה הגלובלי עצמו, ואין ליצור משתנה מקומי. כותבים אותה בתחילת הגוף, ובעזרתה נאסוף את האנרגיה של עומסי התחנה:
energy = 0
def add_load(watts, hours):
global energy
energy = energy + watts * hours
add_load(60, 2)
add_load(100, 0.5)
print(energy, "Wh")170.0 Wh
התוכנית עובדת, אבל מי שקורא את add_load(60, 2) לא יודע שהיא משנה משהו בחוץ, וכשיש הרבה פעולות לא יודעים איזו מהן שינתה את המשתנה. לכן עדיף להעביר את הערך כפרמטר ולהחזיר את התוצאה, וכך הכול נראה בשורת הזימון:
def add_load(energy, watts, hours):
return energy + watts * hours
total = 0
total = add_load(total, 60, 2)
total = add_load(total, 100, 0.5)
print(total, "Wh")170.0 Wh
a = 1
b = 2
def f(a):
global b
a = a + 10
b = b + 10
c = a + b
return c
print(f(a))
print(a, b)7.11 הדגמה: שימוש בפעולה main כנקודת הכניסה לקוד התוכנית
main, ובה נמצא מה שהיה קודם מחוץ לכל הפעולות. התוכנית מתחילה ממנה, והיא המפה שלה: קוראים אותה ויודעים מה התוכנית עושה. כך כל המשתנים נעשים מקומיים, ואפשר לכתוב את ההגדרות בכל סדר. בסגנון הזה נכתבה שאלת הבגרות 2023 בסוף הפרק, ובה הפעולה main_10.קובץ שכתבנו יכול להיות מיובא לתוכנית אחרת בפקודה import, כדי להשתמש בפעולות שלו. אבל הייבוא מריץ את כל מה שיש בקובץ, גם פקודות שמחוץ לפעולות. כדי למנוע זאת, פייתון נותנת לכל קובץ משתנה בשם __name__: ערכו "__main__" כשמריצים את הקובץ עצמו, ושם הקובץ בלי הסיומת .py כשהוא מיובא. ננסה עם קובץ בשם ohm.py, ונריץ אותו ישירות:
def current(v, r):
return v / r
print("name is", __name__)
if __name__ == "__main__":
print(current(12, 480))name is __main__ 0.025
עכשיו נייבא אותו מתוכנית אחרת באותה תיקייה. הפקודה הלא מוגנת רצה, ומה שמתחת לתנאי לא רץ. לכן הפקודה שמתחת לתנאי היא בדרך כלל main():
import ohm print(ohm.current(5, 250))
name is ohm 0.02
main הקצרה מחברת ביניהן ונקראת כמו התרשים שציירנו.def deviation(nominal, measured):
return abs(measured - nominal) * 100 / nominal
def read_batch():
batch = []
value = float(input("Measured (0 to stop): "))
while value != 0:
batch.append(value)
value = float(input("Measured (0 to stop): "))
return batch
def split_batch(nominal, batch, tol=5):
good = []
bad = []
for m in batch:
if deviation(nominal, m) <= tol:
good.append(m)
else:
bad.append(m)
return good, bad
def report(good, bad):
print("Good:", len(good), good)
print("Reject:", len(bad), bad)
def main():
nominal = float(input("Nominal: "))
batch = read_batch()
good, bad = split_batch(nominal, batch)
report(good, bad)
if __name__ == "__main__":
main()Nominal: 1000 Measured (0 to stop): 1020 Measured (0 to stop): 940 Measured (0 to stop): 1049 Measured (0 to stop): 1100 Measured (0 to stop): 0 Good: 2 [1020.0, 1049.0] Reject: 2 [940.0, 1100.0]
def main(): ולא מזמנים אותה. אז התוכנית רצה בלי שגיאה ואינה מדפיסה דבר, כי ההגדרה אינה מבצעת דבר, כמו שראינו בסעיף 7.2.print(__name__) בקובץ שמריצים ישירות?(ב) מה מדפיסה אותה שורה בקובץ
ohm.py כשהוא מיובא?7.12 הוראת pass (כמציין מיקום עבור קוד עתידי) (pass)
גוף של פעולה לא יכול להיות ריק: אם כותבים את ההגדרה ורוצים לחזור אליה אחר כך, התוכנית נעצרת בשגיאה IndentationError. הפקודה pass אינה עושה דבר, וכל תפקידה למלא את המקום. בעזרתה כותבים קודם את השלד של התוכנית, לפי החלוקה, ומריצים אותו לפני שיש בו פעולה אמיתית אחת. התוכנית הבאה היא רושם דגימות, וכל פעולה בה עדיין ריקה:
def read_sample():
pass
def save_sample(sample):
pass
def main():
sample = read_sample()
save_sample(sample)
print("Skeleton runs", sample)
main()Skeleton runs None
השלד רץ, ומכאן שהשמות ומספר הפרמטרים נכונים. הערך None מזכיר לנו שפעולה שגופה pass אינה מחזירה דבר. ממלאים את הפעולות אחת אחת. אפשר גם לגרום ל-read_sample להחזיר ערך קבוע בינתיים, כמו return 25.0, וכך לבדוק את מה שאחריה עוד לפני שיש בידינו חיישן.
pass אינה עוצרת דבר ואינה מדלגת על דבר: התוכנית ממשיכה לשורה הבאה, לעומת break שיוצאת מן הלולאה ו-continue שעוברת לסבב הבא.f() אם הגוף של f הוא pass?(ב) האם אפשר להשתמש ב-
pass בגוף של if?עד עכשיו כתבנו לתחנה תוכניות נפרדות: המרת קריאת הממיר בפרק 2, ההחלטה בפרק 3 ופענוח השורה בפרק 5. עכשיו נאחד אותן לתוכנית אחת, וכל אחת מהן היא פעולה עם תפקיד אחד: adc_to_volts ממירה את קריאת הממיר, parse_line מפענחת את השורה ובודקת אותה, decide מיישמת את כללי פרק 3: מנתקת את העומס מתחת ל-11.8 V, ומפעילה את המאוורר מעל 40 מעלות כשהעומס פועל.
def adc_to_volts(adc, scale=1):
return adc * 5 / 1023 * scale
def is_number(text):
digits = text.replace(".", "")
return text.count(".") <= 1 and digits.isnumeric()
def parse_line(line):
battery = -1.0
temp = -1.0
for item in line.split(";"):
pos = item.find("=")
text = item[pos + 1:]
if pos == -1 or not is_number(text):
return False, 0, 0
if item[:pos] == "BAT":
battery = float(text)
elif item[:pos] == "TMP":
temp = float(text)
return battery >= 0 and temp >= 0, battery, temp
def decide(battery, temp):
load = battery >= 11.8
fan = temp > 40 and load
return load, fan
def report(battery, temp):
load, fan = decide(battery, temp)
print("Battery:", round(battery, 2), "V")
print("Temp:", round(temp, 1), "C")
print("Load on:", load, " Fan on:", fan)
def main():
adc_batt = int(input("ADC battery: "))
adc_temp = int(input("ADC temp: "))
battery = adc_to_volts(adc_batt, 4)
temp = adc_to_volts(adc_temp, 100)
report(battery, temp)
line = input("Line: ")
ok, battery, temp = parse_line(line)
if ok:
report(battery, temp)
else:
print("Line rejected")
if __name__ == "__main__":
main()ADC battery: 620 ADC temp: 82 Battery: 12.12 V Temp: 40.1 C Load on: True Fan on: True Line: BAT=12.42;TMP=31.5 Battery: 12.42 V Temp: 31.5 C Load on: True Fan on: False
הפעולה parse_line מחזירה שלושה ערכים: האם השורה תקינה, המתח והטמפרטורה. כשהיא מוצאת שגיאה היא יוצאת מיד בפקודה return מתוך הלולאה, ולכן אין צורך במונה השגיאות שכתבנו בפרק 5. אלה תוצאות התוכנית על שורות אחרות:
| השורה | הפלט |
|---|---|
BAT=11.5;TMP=46 | Battery: 11.5 V , Temp: 46.0 C , Load on: False Fan on: False |
BAT=12.42;TMP=hot | Line rejected |
BAT=12.42 | Line rejected |
BAT12.42;TMP=31.5 | Line rejected |
והרווח העיקרי הוא שאפשר לבדוק כל פעולה לבדה, בלי שום קלט:
| הזימון | התוצאה |
|---|---|
adc_to_volts(1023) | 5.0 |
is_number("3.1.4") | False |
parse_line("BAT=12.42;TMP=31.5") | (True, 12.42, 31.5) |
parse_line("BAT=12.42") | (False, 12.42, -1.0) |
decide(11.5, 46) | (False, False) |
decide(12.4, 46) | (True, True) |
נסו בעצמכם: רוצים שסף ניתוק העומס יהיה פרמטר עם ערך ברירת מחדל, cutoff=11.8. איזו פעולה משנים? ואילו פעולות נשארות כמו שהן?
עיקרי הפרק:
- מחלקים את המשימה לפני שכותבים, ולכל פעולה תפקיד אחד שאומרים במשפט אחד.
- ההגדרה אינה מבצעת דבר; הגוף מתבצע רק בזימון, ובכל זימון מחדש.
- הזימון שווה לערך המוחזר.
returnלתוכנית ו-printלאדם, ופעולה בליreturnמחזירהNone. - הערכים מתחלקים בין הפרמטרים לפי הסדר, וערכי ברירת המחדל בסוף רשימת הפרמטרים.
- הרשימה שמעבירים היא אותה רשימה, ולכן שינוי של איבר בה מגיע החוצה. השמה לפרמטר אינה מגיעה.
- משתנה מקומי נעלם בסיום הפעולה. עדיפים פרמטר וערך מוחזר על
global. - תוכנית שלמה מתחילה מ-
main, ואת כתיבתה מתחילים משלד עםpass.
סיכום פקודות הפרק:
| פקודה | מה היא עושה |
|---|---|
| def name(a, b): | הגדרת פעולה עם שני פרמטרים |
| name(1, 2) name(b=2, a=1) | זימון לפי הסדר, או לפי שם הפרמטר |
| def f(a, b=2): | פרמטר עם ערך ברירת מחדל, אחרי הפרמטרים הרגילים |
| return x | החזרת ערך וסיום הפעולה |
| return a, b x, y = f() | החזרת שני ערכים כטאפל, ופיזורם |
| return None | יציאה בלי ערך, ומה שחוזר אז |
| def f(*values): | אריזת מספר לא ידוע של ערכים בטאפל |
| f(*items) print(*items) | פירוק רשימה לערכים נפרדים |
| global x | השמה למשתנה הגלובלי מתוך פעולה |
| if __name__ == "__main__": | מתקיים כשמריצים את הקובץ עצמו, ואינו מתקיים כשמייבאים אותו |
| pass | פקודה שאינה עושה דבר וממלאת גוף ריק |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| פעולה · פונקציה | Function | العمليّة · الدالة |
| עידון | Stepwise refinement | التفصيل التدريجي |
| גוף הפעולה | Function body | جسم العمليّة |
| זימון | Call | الاستدعاء |
| פרמטר | Parameter | الوسيط |
| ערך ברירת מחדל | Default value | القيمة الافتراضية |
| ערך מוחזר | Return value | القيمة المُعادة |
| מחרוזת תיעוד | Docstring | جملة التوثيق |
| משתנה מקומי | Local variable | المتغيّر المحلي |
| משתנה גלובלי | Global variable | المتغيّر العام |
| טווח הכרה | Scope | مدى الرؤية |
| אריזה ופירוק | Packing and unpacking | الحزم والفكّ |
| הפעולה הראשית | Main function | العمليّة الرئيسية |
| שלד | Skeleton | الهيكل |
read_temps קוראת את הקריאות לרשימה, highest מחזירה את הקריאה הגבוהה ביותר, count_above מחזירה את מספר הקריאות מעל הסף. כל חלוקה סבירה נכונה.True , False. מדפיסה True אם ברשימה יש לפחות n איברים שערכם 1, ואחרת False.6 , 12 , 7 , 1375.0 , 37.53 ok , 7 high , 7 ok , 9 high , Nonetwice 1 , twice 3 , plus 2 6 , 8| פקודה | m | fix.a | fix.b | fix.c | fix() | c > 5 | פלט |
|---|---|---|---|---|---|---|---|
| m = fix(3, 4) | |||||||
| c = a + b | 3 | 4 | 7 | ||||
| if c > 5: | True | ||||||
| c = c - 5 | 2 | ||||||
| return c | 2 | 2 | |||||
| print(m) | 2 |
[1, 2, 3, 4, 5] , [2, 3, 5]2 , 2 , 2 , 023 , 1 12__main__ , (ב) ohmNone , (ב) כן, בכל מקום שצריך גוף מוזח.שאלות לתרגול:
def f(x, y=3):
return x * y
def g(x):
print(f(x) + 1)
g(2)
print(f(2, 2))
print(g(1))7 , 4 , 4 , Nonedef half_power(v, r):
p = v * v / r
print(p / 2)
result = half_power(12, 48)
print("Half power:", round(result, 2))result מקבל את הערך None. כותבים return:def half_power(v, r):
p = v * v / r
return p / 2
result = half_power(12, 48)
print("Half power:", round(result, 2))Half power: 1.5drop שמקבלת את הזרם ואחריו את ההתנגדות, ומחזירה את מפל המתח על הנגד. ברירת המחדל של ההתנגדות היא 100 Ω.def drop(i, r=100):
return i * r
print(drop(0.02))
print(drop(0.02, 470))2.0 , 9.4def count_between(values, low, high):
count = 0
for v in values:
if low <= v <= high:
count += 1
return count
print(count_between([4.7, 5.1, 5.0, 3.3], 4.9, 5.1))2checksum מחזירה את הסכום הזה, ו-is_intact מזמנת אותה ומחזירה אם המסגרת תקינה.def checksum(frame):
return sum(frame) % 256
def is_intact(frame, received):
return checksum(frame) == received
print(checksum([200, 100, 7]))
print(is_intact([200, 101, 7], 51))51 , Falsedef step(a, b):
a = a + b
b = b * 2
return a, b
x, y = step(1, 2)
x, y = step(x, y)
print(x, y)| פקודה | x | y | step.a | step.b | step() | פלט |
|---|---|---|---|---|---|---|
| x, y = step(1, 2) | ||||||
| a = a + b | 3 | 2 | ||||
| b = b * 2 | 4 | |||||
| return a, b | 3 | 4 | (3, 4) | |||
| x, y = step(x, y) | ||||||
| a = a + b | 7 | 4 | ||||
| b = b * 2 | 8 | |||||
| return a, b | 7 | 8 | (7, 8) | |||
| print(x, y) | 7 8 |
def lowest(*values):
best = values[0]
for v in values:
if v < best:
best = v
return best
volts = [12.1, 11.7, 12.6]
print(lowest(9, 5, 7))
print(lowest(*volts))5 , 11.7index.def band_ohms(bands):
colors = ("black", "brown", "red", "orange", "yellow",
"green", "blue", "violet", "gray", "white")
a = colors.index(bands[0])
b = colors.index(bands[1])
k = colors.index(bands[2])
return (a * 10 + b) * 10 ** k
print(band_ohms(["yellow", "violet", "red"]))
print(band_ohms(["brown", "black", "orange"]))
print(band_ohms("red red brown".split()))4700 , 10000 , 220to_bits שמחזירה מחרוזת עם הספרות הבינריות של מספר, ברוחב קבוע שברירת המחדל שלו 8 סיביות, בלי להשתמש ב-bin. בדקו אותה מול bin.def to_bits(n, width=8):
bits = ""
for k in range(width):
bits = str(n % 2) + bits
n = n // 2
return bits
print(to_bits(5))
print(to_bits(202) == bin(202)[2:])00000101 , Truenetwork שמקבלת את סוג החיבור, "s" לטור או "p" למקביל, וכל מספר של נגדים. חשבו בעזרתה מעגל מורכב: 47 Ω בטור עם שני נגדים של 100 Ω במקביל.def network(kind, *rs):
if kind == "s":
return sum(rs)
total = 0
for r in rs:
total += 1 / r
return round(1 / total, 2)
print(network("s", 100, 220))
print(network("p", 100, 100, 100))
print(network("s", 47, network("p", 100, 100)))320 , 33.33 , 97.0global. כתבו אותה בלעדיו, עם פרמטר וערך מוחזר, כך שהפלט יישאר אותו דבר.count = 0
def beep():
global count
count += 1
print("beep", count)
beep()
beep()def beep(count):
count += 1
print("beep", count)
return count
count = 0
count = beep(count)
count = beep(count)beep 1 , beep 2nearest מסעיף 7.8: כתבו פעולה standard שמחזירה את הערך התקני הקרוב ביותר לכל נגד, גדול ככל שיהיה. מחלקים בעשר עד שהערך נכנס לדקדה הראשונה, מחפשים, ואז כופלים.def nearest(values, target):
best = values[0]
for v in values:
if abs(v - target) < abs(best - target):
best = v
return best
def standard(r):
e12 = [10, 12, 15, 18, 22, 27, 33,
39, 47, 56, 68, 82, 100]
scale = 1
while r >= 100:
r = r / 10
scale = scale * 10
return nearest(e12, r) * scale
print(standard(500))
print(standard(1950))470 , 1800שאלות בגרות לפרק 7
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | הפלט של שלוש הפקודות. | 2 , 4 , 6 |
| (ב) | רשימה שהפעולה מחזירה עבורה 0, ורשימה שהיא מחזירה עבורה 5. | למטה |
| (ג) | הפעולה sum_of_digits. | למטה |
(א) במספר תלת-ספרתי, lst[i] // 100 היא ספרת המאות ו-lst[i] % 10 היא ספרת האחדות, כמו בסעיף 2.9. אם כן, הפעולה סופרת את המספרים שהספרה הראשונה שלהם שווה לאחרונה. את שלושת המספרים נמצא בטבלת התשובות למעלה.
(ב) מספיקה דוגמה אחת לכל ערך. ברשימה [100, 123, 450] אין מספר ששתי ספרות הקצה שלו שוות, ולכן הפעולה מחזירה 0. ברשימה [101, 212, 323, 434, 545] כל המספרים כאלה, והיא מחזירה 5. שתי התוצאות מהרצת הפעולה.
(ג) את הסכום הזה חישבנו ישירות למספר אחד בסוף פרק 2. החישוב אותו חישוב, אבל עכשיו הוא פעולה: הפרמטר במקום הערך הקבוע, ו-return במקום print:
def sum_of_digits(num):
hundreds = num // 100
tens = (num // 10) % 10
ones = num % 10
return hundreds + tens + ones
print(sum_of_digits(472))
print(sum_of_digits(900))13 9
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | הפלט של ארבעת הזימונים. | HHLL , LHHL , LLHH , HLLH |
| (ב) | שתי רשימות שעבורן התוכנית מדפיסה HHHH. | למטה |
| (ג) | הפעולה IsTheSame. | למטה |
(א) זו פעולה שאינה מחזירה ערך: היא מדפיסה H כששני האיברים באותו אינדקס שווים, ו-L כשהם שונים, הכול בשורה אחת בזכות end="", ועוברת לשורה הבאה בפקודה print() האחרונה. נעקוב אחר הזימון השני, ואת ארבע השורות נמצא בטבלת התשובות למעלה:
| פקודה | i | lst1[i] == lst2[i] | פלט |
|---|---|---|---|
| lst1 = [4, 5, 2, 3] | |||
| lst3 = [3, 5, 2, 4] | |||
| DoSomething(lst1, lst3) | |||
| for i in range(len(lst1)): | 0 | ||
| if lst1[i] == lst2[i]: | False | ||
| print("L", end="") | L | ||
| for i in range(len(lst1)): | 1 | ||
| if lst1[i] == lst2[i]: | True | ||
| print("H", end="") | H | ||
| for i in range(len(lst1)): | 2 | ||
| if lst1[i] == lst2[i]: | True | ||
| print("H", end="") | H | ||
| for i in range(len(lst1)): | 3 | ||
| if lst1[i] == lst2[i]: | False | ||
| print("L", end="") | L | ||
| for i in range(len(lst1)): | |||
| print() |
(ב) התשובה HHHH פירושה שכל זוג שווה, כלומר כל שתי רשימות זהות בנות ארבעה איברים, כמו [7, 8, 9, 1] עם עצמה.
(ג) מחפשים הבדל אחד ויוצאים כשמוצאים אותו. קודם בודקים את האורך, כי השאלה מדברת על שתי רשימות זהות, ובלי הבדיקה אפשר לחרוג מגבולות הרשימה הקצרה. בשורה האחרונה נבדוק את המקרה הזה:
def IsTheSame(lst1, lst2):
if len(lst1) != len(lst2):
return False
for i in range(len(lst1)):
if lst1[i] != lst2[i]:
return False
return True
lst1 = [1, 2, 3, 4, 5]
lst2 = [1, 2, 3, 4, 5]
lst3 = [1, 2, 4, 4, 5]
b1 = IsTheSame(lst1, lst2)
b2 = IsTheSame(lst1, lst3)
print(b1, b2)
print(IsTheSame(lst1, [1, 2, 3]))True False False
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | טבלת מעקב לרשימה הנתונה, והערך המוחזר. | False |
| (ב) | רשימה שעבורה מוחזר True ורשימה שעבורה מוחזר False. | למטה |
| (ג) | תפקיד הפעולה במשפט אחד. | למטה |
| (ד) | הפעולה IsInArray. | למטה |
(א) הפקודה return False נמצאת בתוך הלולאה, ולכן ברגע שהתנאי מתקיים פעם אחת יוצאים מן הפעולה כולה ולא מסיימים את הרשימה. השאלה מבקשת עמודה גם לתנאי הלולאה. ללולאת for אין תנאי כתוב, אבל השורה שלה שואלת: האם נשאר איבר לקחת? את התשובה רושמים בעמודה for item in lst, ובעמודה item רואים מה נלקח. העמודה return היא הערך המוחזר:
| פקודה | i | item | return | for item in lst | item % 10 != i | פלט |
|---|---|---|---|---|---|---|
| print(DoSomething(testList)) | ||||||
| i = 0 | 0 | |||||
| for item in lst: | 20 | True | ||||
| if item % 10 != i: | False | |||||
| i = i + 1 | 1 | |||||
| for item in lst: | 41 | True | ||||
| if item % 10 != i: | False | |||||
| i = i + 1 | 2 | |||||
| for item in lst: | 2 | True | ||||
| if item % 10 != i: | False | |||||
| i = i + 1 | 3 | |||||
| for item in lst: | 93 | True | ||||
| if item % 10 != i: | False | |||||
| i = i + 1 | 4 | |||||
| for item in lst: | 14 | True | ||||
| if item % 10 != i: | False | |||||
| i = i + 1 | 5 | |||||
| for item in lst: | 894 | True | ||||
| if item % 10 != i: | True | |||||
| return False | False | False |
באיבר 894 ספרת האחדות היא 4 והמשתנה i שווה ל-5, ולכן התנאי מתקיים והפעולה מחזירה False בלי להגיע לארבעת האיברים שנותרו.
(ב) מוחזר True כשספרת האחדות שווה למקום האיבר ברשימה, ומוחזר False כשהם שונים במקום כלשהו. הרשימה [50, 11, 72, 3] מחזירה True, והרשימה [50, 11, 73, 3] מחזירה False.
(ג) הפעולה בודקת אם ספרת האחדות של כל איבר ברשימה שווה לאינדקס של אותו איבר.
(ד) אותו מבנה: יוצאים עם True באיבר השווה הראשון, ומחזירים False רק אחרי שהלולאה הסתיימה. הטעות הנפוצה היא else: return False בתוך הלולאה, ואז הפעולה נעצרת באיבר הראשון. השאלה כותבת true, ובפייתון זה True:
def IsInArray(lst, num):
for item in lst:
if item == num:
return True
return False
lst = [2, 3, 5, 6, 2, 4, 2]
b1 = IsInArray(lst, 2)
b2 = IsInArray(lst, 7)
print(b1, b2)True False
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | טבלת מעקב והפלט. | True |
| (ב) | הגדרת הבעיה שהפעולה פותרת עבור שתי רשימות שוות בגודלן. | למטה |
| (ג) | הפעולה countEven (בנוסח הערבי CountEven). | למטה |
(א) נוסיף לקוד את השורה main_10() שמפעילה אותו. שתי הרשימות שוות באורכן, ולכן מגיעים ל-else, שם s אוסף את ההפרשים בין האיברים המקבילים. לא כותבים עמודה לשתי הרשימות, כי השאלה אינה מבקשת זאת ודבר בהן אינו משתנה:
| פקודה | i | b | x | y | s | sod() | len(arr1) != len(arr2) | s != 0 | פלט |
|---|---|---|---|---|---|---|---|---|---|
| main_10() | |||||||||
| a1 = [10,15,15,5,5] | |||||||||
| a2 = [10,10,10,10,10] | |||||||||
| print(sod(a1,a2)) | |||||||||
| b = True | True | ||||||||
| if len(arr1) != len(arr2): | False | ||||||||
| s = 0 | 0 | ||||||||
| i = 0 | 0 | ||||||||
| for x in arr1: | 10 | ||||||||
| y = arr2[i] | 10 | ||||||||
| s = s + x - y | |||||||||
| i = i + 1 | 1 | ||||||||
| for x in arr1: | 15 | ||||||||
| y = arr2[i] | |||||||||
| s = s + x - y | 5 | ||||||||
| i = i + 1 | 2 | ||||||||
| for x in arr1: | |||||||||
| y = arr2[i] | |||||||||
| s = s + x - y | 10 | ||||||||
| i = i + 1 | 3 | ||||||||
| for x in arr1: | 5 | ||||||||
| y = arr2[i] | |||||||||
| s = s + x - y | 5 | ||||||||
| i = i + 1 | 4 | ||||||||
| for x in arr1: | |||||||||
| y = arr2[i] | |||||||||
| s = s + x - y | 0 | ||||||||
| i = i + 1 | 5 | ||||||||
| for x in arr1: | |||||||||
| if s != 0: | False | ||||||||
| return b | True | True |
בסוף s שווה לאפס, ולכן b אינו משתנה, והפלט הוא True.
(ב) s הוא סכום הרשימה הראשונה פחות סכום השנייה. לכן: הפעולה מקבלת שתי רשימות שוות בגודלן, ומחזירה True אם סכומי האיברים שלהן שווים, ואחרת מחזירה False. הסדר אינו חשוב, והאיברים יכולים להיות שונים. לכן עבור הרשימות [1, 2, 3] ו-[3, 2, 1] הפעולה מחזירה True.
(ג) מונה עם תנאי מפרק 4, בתוך פעולה שמקבלת רשימה ומחזירה מספר:
def countEven(lst):
count = 0
for x in lst:
if x % 2 == 0:
count += 1
return count
print(countEven([10, 15, 18, 2, 17]))
print(countEven([1, 3]))
print(countEven([]))3 0 0