ייצוג טיפוס חדש על ידי מחלקה
בפרק 1 נתנו פקודות לעצמים של המחלקה המוכנה Turtle, ובסעיף 2.3 למדנו שכל ערך בפייתון הוא עצם של מחלקה כלשהי. עד עכשיו השתמשנו במחלקות שכתבו אחרים. בפרק הזה נכתוב מחלקות בעצמנו: נחליט אילו נתונים הטיפוס החדש נושא ומה הוא יודע לעשות, והוא יהפוך לטיפוס שמשתמשים בו כמו ב-str וב-list. ובסוף הפרק תקבל תחנת המדידה מחלקה לחיישן.
- נסביר מהי מחלקה ומהו עצם, ונתכנן מחלקה שמייצגת טיפוס חדש.
- נכתוב כותרת מחלקה, ונבחין בין משתנה במחלקה למשתנה במופע.
- נגדיר את תכונות העצם בפעולה הבונה, עם פרמטרים אופציונליים.
- נבין את תפקיד
self, ונכתוב פעולות שקוראות תכונות ומשנות אותן, חלקן עם ערכי ברירת מחדל. - נחליט איך עצם מודפס, בעזרת
__str__ו-__repr__.
8.1 הגדרת המושג מחלקה
כל טיפוס שהכרנו עד עכשיו הוא מחלקה מוכנה. הפעולה type אומרת לנו לאיזו מחלקה הערך שייך, והנקודה אחרי הערך מגיעה אל פעולה שכתב יוצר המחלקה:
print(type(12))
print(type("LED"))
print(type([1, 2]))
print("LED".lower())
print([3, 1, 2].count(1))<class 'int'> <class 'str'> <class 'list'> led 1
כדי לראות שמחלקה אחת נותנת עצמים עצמאיים, נחזור אל Turtle מפרק 1 וניצור ממנה שני עצמים:
import turtle
a = turtle.Turtle()
b = turtle.Turtle()
a.color("red")
b.color("blue")
a.forward(100)
b.left(90)
b.forward(60)
print(a.position(), b.position())
print(a.heading(), b.heading())
print(type(a))(100.00,0.00) (0.00,60.00) 0.0 90.0 <class 'turtle.Turtle'>
המחלקה יודעת איך העצם מתקדם ואיך הוא מסתובב, אבל המיקום והכיוון שייכים לכל עצם בנפרד: הפקודה שנתנו ל-a אינה נוגעת ב-b.
למה לכתוב מחלקות משלנו?:
נניח שנרצה לתאר נגדים בתוכנית. במה שלמדנו עד עכשיו נכתוב:
r1_name = "R1" r1_ohms = 220 r2_name = "R2" r2_ohms = 1000 print(r1_name, 12 / r1_ohms) print(r2_name, 12 / r2_ohms)
R1 0.05454545454545454 R2 0.012
התוכנית עובדת, אבל קשה להגדיל אותה. עם עשרה נגדים נצטרך עשרות משתנים, ושום דבר בתוכנית לא אומר ש-r1_name ו-r1_ohms שייכים לאותו רכיב, או שהמשתנה r2_ohms הוא נגד. וגם החישוב חוזר בכל פעם שצריך אותו. מחלקה פותרת את שתי הבעיות יחד: היא מאגדת את נתוני הרכיב בעצם אחד, ומציבה לצדם את הפעולות שעובדות עליהם. כך "נגד" הופך לטיפוס חדש בתוכנית, כמו ש-str הוא טיפוס.
(א)
"LED" ו-str(ב)
Turtle ו-a(ג) "נורית" ו"הנורית האדומה שעל לוח הניסויים".
8.2 פיתוח מחלקה המייצגת טיפוס
לפני שכותבים פקודה אחת עונים על שתי שאלות: מה צריך לדעת על כל רכיב? ומה נרצה לדעת ממנו? התשובה הראשונה נותנת את התכונות, והשנייה נותנת את הפעולות. נפתח את המחלקה בצעדים האלה:
- נקבע מה נרצה לייצג: נגד במעגל.
- נקבע את התכונות: שם הרכיב
name, ערכו באוהםohms, וההספק המרבי שהוא עומד בו בואטwatts. - נקבע את הפעולות: הזרם במתח נתון
current, ההספק במתח נתוןpower, והאם המתח הזה בטוחis_safe. - נכתוב את המחלקה.
- ניצור עצמים, ונבדוק את התוצאות על דוגמה שאנחנו יודעים את תשובתה.
זו המחלקה המלאה, ואיתה תוכנית שמשתמשת בה. נסביר כל חלק בסעיפים הבאים, ועכשיו נסתכל על התמונה הכללית:
class Resistor:
def __init__(self, name, ohms, watts):
self.name = name
self.ohms = ohms
self.watts = watts
def current(self, volts):
return volts / self.ohms
def power(self, volts):
return volts * volts / self.ohms
def is_safe(self, volts):
p = self.power(volts)
return p <= self.watts
r1 = Resistor("R1", 400, 0.25)
r2 = Resistor("R2", 1000, 0.25)
print(r1.name, r1.ohms)
print(r2.name, r2.ohms)
print(r2.current(12))
print(r1.power(12), r1.is_safe(12))
print(r2.power(12), r2.is_safe(12))
print(type(r1))R1 400 R2 1000 0.012 0.36 False 0.144 True <class '__main__.Resistor'>
- הפקודה
class Resistor:פותחת את הגדרת המחלקה, וכל מה שמתחתיה בהזחה שייך לה. - כל
defבתוך המחלקה הוא פעולה של המחלקה. - הפעולה
__init__רצה פעם אחת ביצירת כל עצם, ומציבה את ערכיו בתכונות שלו. - הפקודה
r1 = Resistor("R1", 400, 0.25)יוצרת עצם חדש ומציבה אותו במשתנהr1. - הפקודה
r1.power(12)מזמנת את הפעולה על העצם הזה דווקא, והיא מחשבת לפי ערךohmsשלו.
הבדיקה היא הצעד האחרון בפיתוח: במתח 12 V ובהתנגדות 1000 Ω נצפה לזרם של 0.012 A, וזה מה שהודפס בשורה השלישית. הרכיב r1 אינו עומד במתח הזה, כי ההספק שלו יעבור את 0.25 W. השורה האחרונה בפלט אומרת ש-Resistor הוא עכשיו טיפוס חדש, ו-__main__ פירושו שהוא מוגדר בתוכנית שלנו עצמה.
class Heater:
def __init__(self, volts, ohms):
self.volts = volts
self.ohms = ohms
def amps(self):
return self.volts / self.ohms
h1 = Heater(230, 46)
h2 = Heater(12, 3)
print(h1.amps())
print(h2.amps())
print(h1.ohms + h2.ohms)8.3 כותרת המחלקה, ההבדל בין משתנה במחלקה (class variable) לבין משתנה במופע (instance variable)
כותרת המחלקה היא השורה הראשונה בהגדרתה, ויש בה:
- המילה השמורה
class. - שם המחלקה. נהוג שכל מילה בו מתחילה באות גדולה, כמו
PowerSupply, וכך מבחינים בין המחלקהLedלעצםled. - נקודתיים, ואחריהן גוף המחלקה בהזחה, כמו ב-
ifוב-def.
גוף המחלקה לא יכול להיות ריק. כשעדיין אין מה לכתוב בו, כותבים את הפקודה pass, שלא עושה כלום. גם מחלקה כזו יוצרת עצמים, וכל זימון נותן עצם חדש. האופרטור is שואל: האם שני השמות מציינים את אותו עצם?
class Fuse:
pass
f1 = Fuse()
f2 = Fuse()
print(type(f1))
print(f1 is f2)<class '__main__.Fuse'> False
שני סוגי משתנים:
self.x בתוך פעולה. לכל עצם יש עותק משלו.כל הנוריות בסדנה שלנו הן מדגם שעומד ב-20 mA לכל היותר, אבל לכל צבע מפל מתח אחר. לכן הזרם המרבי הוא משתנה במחלקה, והצבע והמפל הם משתנים במופע. נחשב את הנגד שמחברים בטור עם הנורית:
class Led:
max_current = 0.02
def __init__(self, color, drop):
self.color = color
self.drop = drop
def resistor(self, supply):
v = supply - self.drop
return round(v / Led.max_current)
red = Led("red", 2.0)
blue = Led("blue", 3.2)
print(red.color, blue.color)
print(red.max_current, blue.max_current)
print(Led.max_current)
print(red.resistor(5), blue.resistor(5))
Led.max_current = 0.01
print(red.max_current, blue.max_current)
print(red.resistor(5), blue.resistor(5))red blue 0.02 0.02 0.02 150 90 0.01 0.01 300 180
max_current מן המחלקה, כי אין לו משתנה בשם הזהכשכותבים red.max_current פייתון מחפשת קודם בעצם, לא מוצאת את המשתנה, וקוראת אותו מן המחלקה. לכן כשעדכנו את Led.max_current התוצאה השתנתה בשני העצמים יחד.
| משתנה במחלקה | משתנה במופע | |
|---|---|---|
| איפה כותבים | בגוף המחלקה, מחוץ לפעולות | בתוך פעולה, עם self. |
| כמה עותקים | אחד | אחד לכל עצם |
| איך מגיעים אליו | Led.max_current או red.max_current | red.color בלבד |
| איך משנים | בשם המחלקה: Led.max_current = 0.01 | בשם העצם: red.color = "pink" |
| מתי משתמשים | בערך ששייך לכל הטיפוס | בערך ששייך לרכיב עצמו |
ההפך לא עובד: משתנה במופע אינו נקרא דרך המחלקה, כי המחלקה לא יודעת לאיזה עצם התכוונו, ו-Led.color עוצר את התוכנית בשגיאה AttributeError.
class Relay:
count = 0
def __init__(self, name):
self.name = name
Relay.count = Relay.count + 1
k1 = Relay("K1")
print(Relay.count)
k2 = Relay("K2")
k3 = Relay("K3")
print(Relay.count, k1.count)1 3 3
class Led:
max_current = 0.02
red = Led()
blue = Led()
red.max_current = 0.03
print(red.max_current)
print(blue.max_current)
print(Led.max_current)0.03 0.02 0.02
בגלל זה ספירה בצורה self.count = self.count + 1 לא סופרת כלום: כל עצם קורא את האפס מן המחלקה, שומר את האחד אצלו, והמשתנה במחלקה נשאר אפס. לכן משנים משתנה במחלקה תמיד בשם המחלקה.
class Socket:
volts = 230
def __init__(self, room):
self.room = room
a = Socket("lab")
b = Socket("office")
Socket.volts = 120
b.volts = 12
b.room = "store"
print(a.volts, a.room)
print(b.volts, b.room)
print(Socket.volts)8.4 תכונות המחלקה
נייצג עכשיו משהו שיש בכל מעבדה: שלוש תכונות לספק כוח. שני ערכים נותנים ביצירה, והשלישי, on, מתחיל תמיד ב-False, כי כל ספק מתחיל כבוי, ולכן לא צריך לו פרמטר.
class Supply:
def __init__(self, volts, limit):
self.volts = volts
self.limit = limit
self.on = False
bench = Supply(12, 1.5)
small = Supply(5, 0.5)
print(bench.volts, bench.limit, bench.on)
bench.volts = 9
bench.on = True
print(bench.volts, bench.on)
print(small.volts, small.on)12 1.5 False 9 True 5 False
- בתוך פעולות המחלקה כותבים
self.volts, ומחוץ להן כותבים את שם העצם:bench.volts. - קוראים תכונה ומשנים אותה כמו כל משתנה. השינוי ב-
benchלא נגע ב-small. - תכונה יכולה להיות מכל טיפוס: מספר, מחרוזת,
boolאוlist, ואפילו עצם אחר, כפי שנראה בפרק 9.
bench.volt = 9 יצרה תכונה חדשה בשם volt, ו-volts נשארה כפי שהייתה:class Supply:
def __init__(self, volts):
self.volts = volts
bench = Supply(12)
bench.volt = 9
print(bench.volts)
print(bench.volt)12 9
שני שמות לעצם אחד:
המשתנה לא מכיל את העצם עצמו, אלא מצביע עליו. לכן ההשמה same = bench אינה מעתיקה את העצם אלא נותנת לו שם שני, וכל שינוי דרך אחד השמות נראה גם דרך השני. לעומת זאת other = Supply(12) יצרה עצם אחר, גם אם ערכיו שווים. והאופרטור == בין שני עצמים של המחלקה שלנו שואל את מה ש-is שואל, כל עוד לא לימדנו אותו אחרת, ולכן נתן False:
class Supply:
def __init__(self, volts):
self.volts = volts
bench = Supply(12)
same = bench
other = Supply(12)
same.volts = 5
print(bench.volts, same.volts, other.volts)
print(bench is same, bench is other)
print(bench == other)5 5 12 True False False
מאותה סיבה, פעולה רגילה שמקבלת עצם בפרמטר עובדת על העצם עצמו ולא על עותק שלו, וכל שינוי בתכונותיו נשאר גם אחרי שהיא מסתיימת. זה שונה ממה שראינו בפרק 7 כשמציבים בפרמטר ערך חדש: שם משתנה השם המקומי, וכאן משתנה העצם.
bench.volts = -50 מחוץ למחלקה, וכך נקבל ספק במתח חסר משמעות. בסעיף 8.7 נכתוב פעולה ששומרת על התכונה מערכים כאלה.class Lamp:
def __init__(self, watts):
self.watts = watts
self.hours = 0
desk = Lamp(9)
door = desk
door.hours = 5
door = Lamp(60)
door.hours = door.hours + 2
print(desk.watts, desk.hours)
print(door.watts, door.hours)8.5 פעולות בונות (__init__), פרמטרים אופציונאליים בפעולה בונה
__init__, שרצה אוטומטית פעם אחת ביצירת כל עצם, ותפקידה לתת לתכונותיו את ערכיהן הראשונים.כשכותבים bench = Supply(12, 1.5) קורים שלושה דברים לפי הסדר:
- פייתון יוצרת עצם חדש וריק.
- היא מזמנת את
__init__, ומעבירה אליה את העצם הזה ב-selfואת שאר הערכים בפרמטרים הבאים. - היא מחזירה את העצם, והעצם מוצב במשתנה
bench.
לכן לא מזמנים את __init__ בשמה, ולא כותבים בה return עם ערך: העצם חוזר מעצמו. ובשמה יש שני קווים תחתונים לפני המילה ושניים אחריה.
פרמטרים אופציונליים:
כמו בפעולות הרגילות בפרק 7, נותנים לפרמטר ערך ברירת מחדל בעזרת הסימן =, והוא הופך לאופציונלי ביצירת העצם. רוב סוללות הגיבוי שלנו הן 12 V בקיבולת 7 Ah, ולכן נקבע את שני הערכים האלה כברירת מחדל, ונכתוב רק את מה ששונה:
class Battery:
def __init__(self, name, capacity=7.0,
volts=12):
self.name = name
self.capacity = capacity
self.volts = volts
main = Battery("Main")
back = Battery("Back", 2.2)
cell = Battery("Cell", 2.2, 3.7)
coin = Battery("Coin", volts=3)
print(main.name, main.capacity, main.volts)
print(back.name, back.capacity, back.volts)
print(cell.name, cell.capacity, cell.volts)
print(coin.name, coin.capacity, coin.volts)Main 7.0 12 Back 2.2 12 Cell 2.2 3.7 Coin 7.0 3
- הפרמטר
nameהוא חובה, ושני האחרים אופציונליים. - הערכים מוצבים לפי הסדר, ולכן הערך השני הולך תמיד אל
capacity. - כדי לדלג על פרמטר כותבים את שמו, כמו ב-
volts=3, ו-capacityנשאר בערך ברירת המחדל.
def __init__(self, capacity=7.0, name): היא שגיאת תחביר. ואם לא מעבירים ערך לפרמטר חובה, התוכנית נעצרת בשגיאה TypeError._init_ הופך אותה לפעולה רגילה שלא רצה ביצירה, ופייתון דוחה את הערכים שמעבירים. ושכחת self. הופכת את ההשמה למשתנה מקומי שאובד בסוף הפעולה, והתכונה לא נוצרת:class Battery:
def __init__(self, name):
name = name
main = Battery("Main")
print(main.name)AttributeError: 'Battery' object has no attribute 'name'
class Dimmer:
def __init__(self, level=0):
if level < 0 or level > 100:
level = 0
self.level = level
self.watts = 60 * level / 100
value = int(input("Level: "))
hall = Dimmer(value)
print(hall.level, hall.watts)
wall = Dimmer(250)
print(wall.level, wall.watts)
print(Dimmer().level)Level: 40 40 24.0 0 0.0 0
בשורה האחרונה הפרמטר level מקבל את ערך ברירת המחדל. זו אותה בדיקה שעושה שאלת המלבן בבגרות שבסוף הפרק.
class Probe:
def __init__(self, name, data=[]):
self.name = name
self.data = data
p1 = Probe("P1")
p2 = Probe("P2")
p1.data.append(5)
print(p2.data)
print(p1.data is p2.data)[5] True
הפתרון הוא ליצור את הרשימה בתוך הפעולה הבונה, בשורה self.data = [], וכך כל עצם מקבל רשימה משלו.
class Motor:
def __init__(self, volts=24, rpm=0):
if rpm < 0:
rpm = 0
self.volts = volts
self.rpm = rpm
m1 = Motor()
m2 = Motor(12)
m3 = Motor(rpm=-300)
m4 = Motor(48, 900)
print(m1.volts, m1.rpm)
print(m2.volts, m2.rpm)
print(m3.volts, m3.rpm)
print(m4.volts, m4.rpm)8.6 אופרטור self
פעולה כתובה במחלקה פעם אחת, אבל היא עובדת על עצמים רבים. אז איך היא יודעת על איזה עצם לעבוד? התשובה היא הפרמטר הראשון, self: כשכותבים a.drain(2), פייתון מעבירה את העצם שלפני הנקודה אל self. הפקודות a.drain(2) ו-Cell.drain(a, 2) שקולות לגמרי, וגם בצורה השנייה השתמשנו בתוכנית הבאה:
class Cell:
def __init__(self, name, charge):
self.name = name
self.charge = charge
def drain(self, amount):
self.charge = self.charge - amount
print(self.name, self.charge)
a = Cell("A", 10)
b = Cell("B", 30)
a.drain(2)
b.drain(5)
Cell.drain(a, 1)
print(a.charge, b.charge)A 8 B 25 A 7 7 25
self מצביע בכל זימון על העצם שלפני הנקודההכללים של self:
selfהוא הפרמטר הראשון בכל פעולה. כותבים אותו בהגדרה, ולא כותבים אותו בזימון.- בתוך הפעולה מגיעים דרכו לכל תכונה ולכל פעולה אחרת:
self.chargeו-self.power(volts). - תכנית הלימודים קוראת לו "אופרטור", אבל למעשה הוא פרמטר רגיל. והשם
selfאינו מילה שמורה, אלא מוסכמה שכולם מקפידים עליה, וגם אנחנו.
השם self.charge שונה לגמרי מ-charge, גם אם כותבים אותם באותה שורה. self.charge היא תכונה שנשארת בעצם, ו-charge הוא פרמטר שנעלם בסוף הפעולה, וכמוהו המשתנה המקומי before:
class Cell:
def __init__(self, charge):
self.charge = charge
def add(self, charge):
before = self.charge
self.charge = self.charge + charge
return self.charge - before
a = Cell(10)
print(a.add(4))
print(a.charge)4 14
self בהגדרת הפעולה. פייתון מעבירה אותו בכל זאת, ולפעולה מגיע פרמטר שלא ציפתה לו. ההודעה מדברת על אפס פרמטרים ועל פרמטר אחד, אף שלא העברנו כלום:class Cell:
def status():
return "ready"
a = Cell()
print(a.status())TypeError: Cell.status() takes 0 positional arguments but 1 was given
self. בתוך הפעולה, פייתון מחפשת משתנה רגיל בשם הזה, והתוכנית נעצרת בשגיאה NameError.pour מזמנת פעולה על שני עצמים שונים.class Tank:
def __init__(self, level):
self.level = level
def fill(self, liters):
self.level = self.level + liters
def pour(self, other, liters):
self.fill(-liters)
other.fill(liters)
x = Tank(50)
y = Tank(10)
x.pour(y, 20)
y.fill(5)
print(x.level, y.level)8.7 פעולות המחלקה, כולל פרמטרים עם ערך ברירת מחדל
לפעולות המחלקה יש שני סוגים עיקריים: פעולה שואלת קוראת תכונות ומחזירה תשובה, ואינה משנה את העצם, ופעולה מצווה משנה תכונה אחת או יותר, ובדרך כלל לא מחזירה כלום. כל אחת מהן יכולה לזמן פעולה אחרת באותה מחלקה דרך self.
full, 10 Ah, ונעקוב אחר המטען שנשאר בה left. הפעולות is_low ו-use עובדות גם בלי להעביר את כל הפרמטרים: סף ברירת המחדל לסוללה כמעט ריקה הוא 20 אחוז, ומשך ברירת המחדל הוא שעה אחת.class Battery:
def __init__(self, name, full):
self.name = name
self.full = full
self.left = full
def percent(self):
return 100 * self.left / self.full
def is_low(self, limit=20):
return self.percent() < limit
def use(self, amps, hours=1):
self.left -= amps * hours
if self.left < 0:
self.left = 0
def recharge(self):
self.left = self.full
pack = Battery("Pack", 10)
print(pack.percent(), pack.is_low())
pack.use(2)
print(pack.percent(), pack.is_low())
pack.use(1.5, 4.5)
print(pack.left, pack.percent())
print(pack.is_low(), pack.is_low(10))
pack.use(amps=3, hours=9)
print(pack.left)
pack.recharge()
print(pack.left)100.0 False 80.0 False 1.25 12.5 True False 0 10
נשים לב לשלושה דברים בפלט. ב-use יש פרמטר חובה אחד ופרמטר אופציונלי אחד, ולכן כתבנו פעם use(2) ופעם use(1.5, 4.5). אחר כך is_low() בערך ברירת המחדל מול is_low(10) בסף אחר. ובזימון use(amps=3, hours=9) השתמשנו בשמות הפרמטרים, והמטען לא ירד מתחת לאפס בזכות התנאי שבתוך הפעולה.
פעולה ששומרת על תכונה:
בסעיף 8.4 ראינו שכתיבה ישירה בתכונה מקבלת כל ערך. כשחשוב לנו שהתכונה תהיה תקינה, משנים אותה בפעולה שבודקת קודם, ומחזירה True או False כדי לומר אם הצליחה. כך עובדת הפעולה שמשלמת על נסיעה בשאלת הבגרות שבסוף הפרק. הגבול העליון שייך לכל הספקים, ולכן הוא משתנה במחלקה:
class Supply:
MAX = 30
def __init__(self):
self.volts = 0
def set_volts(self, volts):
if volts < 0 or volts > Supply.MAX:
return False
self.volts = volts
return True
bench = Supply()
print(bench.set_volts(12), bench.volts)
print(bench.set_volts(50), bench.volts)
print(bench.set_volts(-3), bench.volts)True 12 False 12 False 12
עצם כפרמטר, ועצם כתוצאה:
פרמטר יכול להיות עצם מאותה מחלקה, ופעולה יכולה להחזיר עצם חדש. חיבור של שני נגדים נותן נגד חדש, שערכו:
class Resistor:
def __init__(self, name, ohms):
self.name = name
self.ohms = ohms
def series(self, other):
name = self.name + "+" + other.name
total = self.ohms + other.ohms
return Resistor(name, total)
def parallel(self, other):
name = self.name + "|" + other.name
both = self.ohms + other.ohms
total = self.ohms * other.ohms
return Resistor(name, total / both)
r1 = Resistor("R1", 100)
r2 = Resistor("R2", 300)
r3 = Resistor("R3", 50)
s = r1.series(r2)
print(s.name, s.ohms)
n = r1.parallel(r2).series(r3)
print(n.name, n.ohms)
print(r1.ohms, r2.ohms)R1+R2 400 R1|R2+R3 125.0 100 300
הפעולה series עובדת על שני עצמים: self הוא זה שלפני הנקודה, ו-other הוא זה שבסוגריים. ומכיוון שהתוצאה היא עצם, אפשר לכתוב אחריה מיד נקודה נוספת, כמו ב-r1.parallel(r2).series(r3). שני הנגדים המקוריים לא השתנו, כפי שמראה השורה האחרונה.
pack.percent אינו נותן מספר. ותכונה ופעולה באותו שם מתנגשות: אם מציבים ב-self.power וכותבים גם פעולה power(), התכונה מסתירה את הפעולה אצל העצם הזה. לכן נותנים להן שמות שונים.return מחזירה את הערך None, ואין טעם לשמור את התוצאה שלה:class Cell:
def __init__(self, charge):
self.charge = charge
def drain(self, amount):
self.charge = self.charge - amount
a = Cell(10)
result = a.drain(4)
print(result)
print(a.charge)None 6
class Timer:
def __init__(self, limit=60):
self.limit = limit
self.time = 0
def tick(self, seconds=1):
self.time = self.time + seconds
def left(self):
return self.limit - self.time
def done(self):
return self.left() <= 0
t = Timer(10)
t.tick()
t.tick(4)
print(t.time, t.left(), t.done())
t.tick(seconds=5)
print(t.time, t.left(), t.done())
print(Timer().limit)8.8 העמסת הפעולה __repr__ או __str__ לצורך החזרת מחרוזת לתיאור העצם
מה קורה כשמדפיסים עצם של המחלקה שלנו?
class Resistor:
def __init__(self, name, ohms):
self.name = name
self.ohms = ohms
r1 = Resistor("R1", 220)
print(r1)<__main__.Resistor object at 0x000001F2A4C8>
פייתון לא יודעת איך נרצה לתאר את הנגד, ולכן היא מדפיסה את שם המחלקה ואת כתובת העצם בזיכרון. הכתובת הזו משתנה מהרצה להרצה, ואצלכם לא יופיע אותו מספר שמופיע כאן. בספר קיבענו אותו כדי שהפלט לא ישתנה, אבל בכל מקרה הוא לא עוזר לנו.
__str__, כך שהגרסה שלנו פועלת במקום ההתנהגות המוכנה.הפעולה __str__ מקבלת רק את self, ומחזירה מחרוזת שמתארת את העצם. הפקודות print(r1) ו-str(r1) מזמנות אותה מעצמן:
class Resistor:
def __init__(self, name, ohms):
self.name = name
self.ohms = ohms
def __str__(self):
text = str(self.ohms) + " ohm"
return self.name + " = " + text
r1 = Resistor("R1", 220)
print(r1)
text = str(r1)
print(len(text))
print("Part:", r1)
print("Part: " + str(r1))R1 = 220 ohm 12 Part: R1 = 220 ohm Part: R1 = 220 ohm
print במקום return. ההדפסה קורית, ואז לא חוזר כלום, והתוכנית נעצרת:class Resistor:
def __init__(self, ohms):
self.ohms = ohms
def __str__(self):
print(self.ohms, "ohm")
r1 = Resistor(220)
print(r1)220 ohm TypeError: __str__ returned non-string (type NoneType)
והערך שחוזר חייב להיות מחרוזת, גם אם התכונה עצמה מספר. לכן return self.ohms נותן את אותה שגיאה, וממירים בעזרת str.
הפעולה __repr__:
__str__ מתארת את העצם לקורא, ו-__repr__ מתארת אותו למתכנת, ונהוג שתיתן מה שדומה לפקודה שיוצרת עצם כזה. ההבדל המעשי חשוב: כשמדפיסים רשימה של עצמים, מזומנת __repr__ לכל איבר:
class Part:
def __init__(self, ohms):
self.ohms = ohms
def __str__(self):
return str(self.ohms) + " ohm"
def __repr__(self):
text = str(self.ohms)
return "Part(" + text + ")"
part = Part(220)
print(part)
print(repr(part))
print([part, Part(470)])220 ohm Part(220) [Part(220), Part(470)]
| מה כותבים | מה מזומן |
|---|---|
print(x) , str(x) | __str__, ואם אין אז __repr__ |
repr(x) | __repr__ |
print([x, y]) | __repr__ לכל איבר ברשימה |
אם כותבים פעולה אחת בלבד, הכי פשוט שתהיה __str__. אבל אם כותבים רק __str__ ומדפיסים רשימה, הכתובות מופיעות שוב. ואם רוצים פעולה אחת שתשמש גם להדפסה וגם לרשימות, כותבים רק את __repr__.
בניית מחרוזת בעזרת הקידומת f:
בניית התיאור בשרשור וב-str ארוכה. הקידומת f לפני המירכאות מאפשרת לכתוב בתוך המחרוזת ביטוי בסוגריים מסולסלים, וערכו מוצב במקומו ומומר למחרוזת מעצמו. נפגוש את הצורה הזו בשאלת המלבן בבגרות:
class Resistor:
def __init__(self, ohms):
self.ohms = ohms
def __str__(self):
return f"R = {self.ohms} ohm"
r1 = Resistor(220)
print(r1)
print(f"Twice: {r1.ohms * 2}")R = 220 ohm Twice: 440
class Bulb:
def __init__(self, watts):
self.watts = watts
self.on = False
def switch(self):
self.on = not self.on
def __str__(self):
text = str(self.watts) + " W: "
if self.on:
return text + "ON"
return text + "OFF"
b = Bulb(40)
print(b)
b.switch()
print(b)
print("Bulb " + str(b))בפרק 2 חישבנו את מתח הסוללה ואת הטמפרטורה בשתי שורות דומות, ולכל אחת המשתנים שלה. עכשיו נראה מה משותף: לכל חיישן יש שם, יחידה וקריאה גולמית מן הממיר, ויש לו מקדם scale שהופך את המתח בכניסת הממיר לערך האמיתי: 4 למחלק המתח שעל הסוללה, ו-100 לחיישן הטמפרטורה שנותן 10 mV לכל מעלה. הצעד step שייך לממיר עצמו, והממיר אחד לכל החיישנים, ולכן הוא משתנה במחלקה.
class Sensor:
step = 5 / 1023
def __init__(self, name, unit, scale):
self.name = name
self.unit = unit
self.scale = scale
self.raw = 0
def read(self, raw):
self.raw = raw
def value(self):
volts = self.raw * Sensor.step
return round(volts * self.scale, 2)
def __str__(self):
value = str(self.value())
text = self.name + ": " + value
return text + " " + self.unit
battery = Sensor("Battery", "V", 4)
temp = Sensor("Temp", "C", 100)
print(battery)
battery.read(int(input("ADC battery: ")))
temp.read(int(input("ADC temp: ")))
print(battery)
print(temp)
print(battery.value() > 11.5)Battery: 0.0 V ADC battery: 620 ADC temp: 82 Battery: 12.12 V Temp: 40.08 C True
אותה קריאה שהזנו בפרק 2 נותנת כאן את אותו מתח, וזו בדיקה טובה. לפני הקריאה הראשונה raw הוא אפס, ולכן השורה הראשונה הדפיסה אפס. והוספת חיישן חדש דורשת שורה אחת, בלי לחזור על שום חישוב.
נסו בעצמכם: בלוח ESP32 הממיר הוא בן 12 סיביות והמתח שלו 3.3 V. איזו שורה אחת צריך להוסיף כדי שכל החיישנים יעבדו עם הממיר הזה? ובפרק הבא החיישנים האלה ייכנסו אל מחלקת התחנה עצמה.
עיקרי הפרק:
- מחלקה היא תיאור כללי, ועצם הוא פריט מסוים עם ערכים משלו, וממחלקה אחת יוצרים כמה עצמים שרוצים.
- מתכננים מחלקה בשתי שאלות: מה צריך לדעת על העצם? (תכונות) ומה נרצה ממנו? (פעולות).
- למשתנה במחלקה עותק אחד משותף, ומשנים אותו בשם המחלקה. משתנה במופע שייך לכל עצם בנפרד.
- הפעולה הבונה
__init__נותנת לתכונות את ערכיהן הראשונים, ופרמטרים אופציונליים באים אחרי פרמטרי החובה. selfהוא העצם שלפני הנקודה. בליself.מתקבל משתנה מקומי שאובד.- ההשמה
b = aאינה מעתיקה את העצם, אלא נותנת לו שם שני. __str__מחזירה מחרוזת ולא מדפיסה, ו-__repr__מתארת את העצם בתוך רשימה.
סיכום פקודות הפרק:
| פקודה | מה היא עושה |
|---|---|
| class Led: | כותרת המחלקה, ומתחתיה גופה בהזחה |
| max_current = 0.02 | בגוף המחלקה: משתנה במחלקה |
| def __init__(self, a, b=1): | הפעולה הבונה, ו-b פרמטר אופציונלי |
| self.x = a | תכונה, כלומר משתנה במופע |
| led = Led(2.0) | יצירת עצם |
| led.x Led.max_current | גישה לתכונה, ולמשתנה במחלקה |
| led.f() led.f(3) led.f(n=3) | זימון פעולה, ו-n יכול לקבל ערך ברירת מחדל |
| def __str__(self): | מה ש-print מדפיס לעצם: מחזירה מחרוזת |
| def __repr__(self): | תיאור העצם למתכנת ובתוך רשימות |
| f"{self.x} V" | מחרוזת עם ביטויים בסוגריים מסולסלים |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| מחלקה | Class | الفئة |
| עצם | Object | الكائن |
| מופע | Instance | النسخة |
| תכונה | Attribute | الصفة |
| פעולה | Method | العمليّة |
| כותרת המחלקה | Class header | رأس الفئة |
| משתנה במחלקה | Class variable | متغيّر الفئة |
| משתנה במופע | Instance variable | متغيّر النسخة |
| פעולה בונה | Constructor | عمليّة البناء |
| פרמטר אופציונלי | Optional parameter | وسيط اختياري |
| ערך ברירת מחדל | Default value | قيمة افتراضية |
| העמסה | Overloading | التحميل الزائد |
str מחלקה ו-"LED" עצם , (ב) Turtle מחלקה ו-a עצם , (ג) הראשון מחלקה והשני עצם.5.0 , 4.0 , 49120 lab , 12 store , 1209 5 , 60 224 0 , 12 0 , 24 0 , 48 90030 355 5 False , 10 0 True , 6040 W: OFF , 40 W: ON , Bulb 40 W: ONשאלות לתרגול:
class Fan:
speed = 1200
def __init__(self, name):
self.name = name
a = Fan("A")
b = Fan("B")
b.speed = 900
Fan.speed = 1500
print(a.speed, b.speed, Fan.speed)1500 900 1500class Lamp:
def __init__(self, watts):
watts = watts
def energy(hours):
return self.watts * hours / 1000
desk = Lamp(9)
print(desk.energy(100))self.watts = בפעולה הבונה, וחסר הפרמטר self בהגדרת energy:class Lamp:
def __init__(self, watts):
self.watts = watts
def energy(self, hours):
return self.watts * hours / 1000
desk = Lamp(9)
print(desk.energy(100))0.9Fuse עם התכונה rating והפעולה blows(amps), שמחזירה True אם הזרם גדול מערך הנתיך.class Fuse:
def __init__(self, rating):
self.rating = rating
def blows(self, amps):
return amps > self.rating
f = Fuse(16)
print(f.blows(16), f.blows(20))False TrueSwitch למתג שמתחיל פתוח, עם הפעולה toggle שהופכת את מצבו, ו-__str__ שמחזירה את שמו ואת מצבו.class Switch:
def __init__(self, name):
self.name = name
self.on = False
def toggle(self):
self.on = not self.on
def __str__(self):
if self.on:
return self.name + ": ON"
return self.name + ": OFF"
s = Switch("S1")
print(s)
s.toggle()
print(s)S1: OFF , S1: ONCapacitor עם קיבול ומתח שערך ברירת המחדל שלו 12 V, עם הפעולה charge, Q = C · V, הפעולה energy, E = C · V2 2, והפעולה __str__.class Capacitor:
def __init__(self, c, v=12):
self.c = c
self.v = v
def charge(self):
return self.c * self.v
def energy(self):
e = self.c * self.v ** 2 / 2
return round(e, 4)
def __str__(self):
text = str(self.c) + " F, "
return text + str(self.v) + " V"
c = Capacitor(0.001)
print(c)
print(c.charge(), c.energy())
d = Capacitor(0.002, 5)
print(d)
print(d.charge(), d.energy())0.001 F, 12 V , 0.012 0.072 , 0.002 F, 5 V , 0.01 0.025PulseCounter למונה פולסים: מתחילה מאפס, עם pulse שמוסיפה n פולסים וערך ברירת המחדל שלה 1, ועם reset.class PulseCounter:
def __init__(self):
self.count = 0
def pulse(self, n=1):
self.count = self.count + n
def reset(self):
self.count = 0
c = PulseCounter()
c.pulse()
c.pulse()
c.pulse(5)
print(c.count)
c.reset()
c.pulse(2)
print(c.count)7 , 2Part לנגד שערכו ידוע ויש לו סבילות באחוזים, שערך ברירת המחדל שלה 5. בה low ו-high שנותנות את גבולות התחום, ו-accepts שבודקת ערך מדוד.class Part:
def __init__(self, ohms, tol=5):
self.ohms = ohms
self.tol = tol
def low(self):
d = self.ohms * self.tol / 100
return self.ohms - d
def high(self):
d = self.ohms * self.tol / 100
return self.ohms + d
def accepts(self, x):
return self.low() <= x <= self.high()
r = Part(1000)
print(r.low(), r.high())
print(r.accepts(1040), r.accepts(1060))
s = Part(1000, 1)
print(s.accepts(1040))950.0 1050.0 , True False , FalseBoard שעושה זאת בלי להעביר את המספר ביצירה.class Board:
next_id = 100
def __init__(self, model):
self.model = model
self.serial = Board.next_id
Board.next_id = Board.next_id + 1
def __str__(self):
text = str(self.serial)
return self.model + "-" + text
a = Board("ESP")
b = Board("UNO")
c = Board("ESP")
print(a, b, c)
print(Board.next_id)ESP-100 UNO-101 ESP-102 , 103. המספר הבא שייך לכל המחלקה, ולכן הוא משתנה במחלקה, והמספר של כל לוח הוא משתנה במופע.class Led:
def __init__(self, color, volts, amps):
self.color = color
self.volts = volts
self.amps = amps
def power(self):
return self.volts * self.amps
leds = [Led("red", 2.0, 0.02),
Led("blue", 3.2, 0.02),
Led("white", 3.0, 0.35)]
best = leds[0]
for led in leds:
if led.power() > best.power():
best = led
print(best.color, round(best.power(), 2))white 1.05Uptime ששומרת את זמן הפעולה של התחנה בשניות, עם tick(n=1), ועם __str__ שמחזירה את הזמן בצורה hh:mm:ss. פעולת עזר משלימה אפס לפני מספר בן ספרה אחת.class Uptime:
def __init__(self, seconds=0):
self.seconds = seconds
def tick(self, n=1):
self.seconds = self.seconds + n
def two(self, n):
if n < 10:
return "0" + str(n)
return str(n)
def __str__(self):
h = self.seconds // 3600
m = self.seconds % 3600 // 60
s = self.seconds % 60
text = self.two(h) + ":"
text = text + self.two(m) + ":"
return text + self.two(s)
t = Uptime(3599)
print(t)
t.tick()
print(t)
t.tick(7384)
print(t)00:59:59 , 01:00:00 , 03:03:04Signal ששומרת רשימת דגימות של אות, עם add, mean, peak ו-__str__. כל עצם צריך לקבל רשימה משלו.class Signal:
def __init__(self, name):
self.name = name
self.data = []
def add(self, value):
self.data.append(value)
def mean(self):
n = len(self.data)
return sum(self.data) / n
def peak(self):
return max(self.data)
def __str__(self):
n = str(len(self.data))
text = self.name + ": " + n
return text + " samples"
s = Signal("A0")
for x in [2, 4, 9, 5]:
s.add(x)
print(s)
print(s.mean(), s.peak())A0: 4 samples , 5.0 9Reg8 לאוגר בן 8 סיביות: is_set(bit), set_bit(bit) ו-clear_bit(bit) בחשבון בלבד, ו-__str__ שנותנת את שמונה הסיביות מן הגבוהה לנמוכה.class Reg8:
def __init__(self, value=0):
self.value = value
def is_set(self, bit):
v = self.value // 2 ** bit
return v % 2 == 1
def set_bit(self, bit):
if not self.is_set(bit):
self.value += 2 ** bit
def clear_bit(self, bit):
if self.is_set(bit):
self.value -= 2 ** bit
def __str__(self):
text = ""
for bit in range(7, -1, -1):
if self.is_set(bit):
text = text + "1"
else:
text = text + "0"
return text
r = Reg8()
r.set_bit(0)
r.set_bit(3)
r.set_bit(3)
print(r, r.value)
r.clear_bit(0)
r.clear_bit(5)
print(r, r.value)00001001 9 , 00001000 8. בודקים לפני שמוסיפים או מחסרים, ולכן שום דבר לא השתנה כשחזרנו על סיבית 3, וגם לא כשניקינו את סיבית 5.שאלות בגרות לפרק 8
addNum ו-DoSomething הם השמות שבשאלון, ואנחנו משאירים אותם כפי שהם גם אם אינם בסגנון של הספר.| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | פעולה בונה ללא פרמטרים שמאתחלת את שתי התכונות לאפס, ופעולה display שמדפיסה את ערכיהן. | למטה |
| (ב) | מה מדפיס קטע הקוד הנתון? | Average is 0.0 |
| (ג) | שנו את addNum כך שרק ציונים מ-0 עד 100 ייכנסו לחישוב. | למטה |
(א) הקוד הנתון נשען על שתי תכונות שאף אחד לא יוצר, ובלי פעולה בונה התוכנית נעצרת בזימון הראשון בשגיאה AttributeError. נוסיף את שתי הפעולות הנדרשות, ונריץ את הקטע שבסעיף (ב). הוספנו לו בסוף את השורה a.display() רק כדי לבדוק. סכום חמשת המספרים הוא אפס, ולכן הממוצע אפס, ותוצאת החילוק היא מספר עשרוני:
class Average:
def __init__(self):
self.sum = 0
self.count = 0
def addNum(self, num):
self.sum = self.sum + num
self.count = self.count + 1
def calcAverage(self):
return self.sum / self.count
def display(self):
print(self.sum, self.count)
arr = [90, 100, -40, -60, -90]
a = Average()
for item in arr:
a.addNum(item)
x = a.calcAverage()
print("Average is", x)
a.display()Average is 0.0 0 5
(ג) נעטוף את שתי השורות בתנאי, וכך מספר מחוץ לתחום לא נכנס לסכום ולא לכמות. נשנה רק את הפעולה addNum:
def addNum(self, num):
if 0 <= num <= 100:
self.sum = self.sum + num
self.count = self.count + 1עכשיו עוברים רק המספרים 90 ו-100, ואותו קטע מדפיס: Average is 95.0 , 190 2.
count נשאר אפס, והחילוק גורם לשגיאה. השאלה לא דורשת לטפל במקרה הזה, אבל כדאי לשים לב אליו.| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) 1 | מה מדפיס קטע הקוד? | False , True , False , True , False |
| (א) 2 | במשפט אחד: מה מבצעת DoSomething? | בודקת אם המספר נופל על אחד מצעדי הרשימה, כלומר אם ההפרש בינו לבין start הוא כפולה של step. |
| (ב) | הפעולה printRange מדפיסה את איברי הרשימה. | למטה |
| (ג) | הפעולה getNumberByIndex מחזירה את האיבר שבאינדקס נתון. | למטה |
(א) הרשימה כאן היא 10 , 14 , 18. נריץ את הקטע, והוא מדפיס True עבור שני המספרים שנמצאים בה מבין אלה שבדקנו:
class ListClass:
def __init__(self, start, end, step):
self.start = start
self.end = end
self.step = step
def DoSomething(self, number):
if (number - self.start) % self.step != 0:
return False
return True
lst = ListClass(10, 21, 4)
print(lst.DoSomething(11))
print(lst.DoSomething(14))
print(lst.DoSomething(16))
print(lst.DoSomething(18))
print(lst.DoSomething(21))False True False True False
start הוא כפולה של step, ולכן היא נותנת להם True: True True. בדיקה מלאה דורשת גם את התנאי self.start <= number <= self.end. ובנוסח השאלה כתוב steep, ובקוד step, והנכון הוא מה שבקוד.(ב) נתחיל מ-start, ונוסיף בכל סבב step, כל עוד לא עברנו את end. המספר 12 לא מופיע בדוגמה, כי הוא רק האיבר האחרון האפשרי, והצעד קופץ מ-11 ל-13. נדפיס עם הפרמטר end=" " כדי שהמספרים יישארו בשורה אחת. אפשר גם לכתוב את הלולאה for number in range(self.start, self.end + 1, self.step). (ג) לא צריך לולאה: האיבר שהאינדקס שלו index מרוחק index צעדים מן ההתחלה, והאינדקס של האיבר הראשון הוא 0:
def printRange(self):
number = self.start
while number <= self.end:
print(number, end=" ")
number = number + self.step
print()
def getNumberByIndex(self, index):
return self.start + index * self.stepעבור העצם ListClass(5, 12, 2) שתי הדוגמאות שבשאלה מדפיסות: 5 7 9 11 , 7.
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | מה מדפיס קטע הקוד? | למטה |
| (ב) | הפעולה IsSquare מחזירה True אם המלבן הוא ריבוע, ואחרת False. | self.length == self.width |
| (ג) | ניצור שני מלבנים, 25 על 17 ו-25 על 25, ונבדוק כל אחד מהם. | False , True |
(א) הפעולה הבונה בודקת את הערכים, כמו בסעיף 8.5. המספר −1 שלילי, ולכן מתבצע ענף else, והאורך והרוחב הופכים ל-2. הפעולה DoSomething מחשבת למעשה את שטח המלבן. אחר כך הלולאה עוברת על הערכים −1 , 0 , 1 , 2, וכולם משאירים את האורך חיובי, ולכן כל שינוי מתקבל. בהצגת התוצאה משתמשים בקידומת f מסעיף 8.8:
class Rectangle:
def __init__(self, length, width):
if length >= 0 and width >= 0:
self.length = length
self.width = width
else:
self.length = 2
self.width = 2
def AddToDimensions(self, lengthToAdd, widthToAdd):
if (self.length + lengthToAdd > 0
and self.width + widthToAdd > 0):
self.length = self.length + lengthToAdd
self.width = self.width + widthToAdd
def DoSomething(self):
return self.length * self.width
def print_details(self):
print(f"L={self.length} W={self.width}", end=" ")
print(f"D1={self.DoSomething()}")
rect = Rectangle(-1, 1)
rect.print_details()
for i in range(-1, 3):
rect.AddToDimensions(i, 0)
rect.print_details()L=2 W=2 D1=4 L=1 W=2 D1=2 L=1 W=2 D1=2 L=2 W=2 D1=4 L=4 W=2 D1=8
חילקנו את התנאי של AddToDimensions לשתי שורות בתוך סוגריים כדי שיתאים לעמוד, והמשמעות כמו בשאלה. בשאלה ההזחה כתובה לעיתים בפחות מארבעה רווחים, וזה מותר כל עוד היא אחידה בכל קטע.
(ב) ההשוואה עצמה נותנת את הערך הנדרש, ולכן אין צורך ב-if. בשאלה כתוב true ו-false, ובפייתון שני הערכים נכתבים באות גדולה. (ג) ניצור את שני העצמים, ונזמן את הפעולה על כל אחד:
def IsSquare(self):
return self.length == self.widthrect1 = Rectangle(25, 17) rect2 = Rectangle(25, 25) print(rect1.IsSquare()) print(rect2.IsSquare())
התוכנית מדפיסה: False , True.
| סעיף | המבוקש | תשובה |
|---|---|---|
| (א) | מה מדפיס קטע הקוד? | למטה |
| (ב) | הדפיסו את שם בעל הכרטיס עם היתרה הגבוהה מבין שני כרטיסים. | פתרנו בסוף פרק 3 |
| (ג) | הוסיפו תכונה שסופרת נסיעות שבוצעו בהצלחה, ועדכנו את pay_for_trip ואת get_info. | למטה |
(א) היתרה 55.5, והיא נעשית 75.5 אחרי הטעינה. התשלום הראשון מצליח, ונשאר 35.5. השני נכשל כי היתרה קטנה מ-40, ולכן חוזר False והיתרה לא משתנה. חילקנו את השורה של get_info כי היא ארוכה, והתוצאה אותה תוצאה:
class TravelCard:
def __init__(self, owner_name, balance):
self.owner_name = owner_name
self.balance = balance
def top_up(self, amount):
if amount > 0:
self.balance = self.balance + amount
def pay_for_trip(self, cost):
if self.balance >= cost:
self.balance = self.balance - cost
return True
return False
def get_info(self):
text = self.owner_name + " has "
text = text + str(self.balance)
return text + " shekels left."
card = TravelCard("Guest", 55.5)
card.top_up(20)
print("Pay 40:", card.pay_for_trip(40))
print("Pay 40:", card.pay_for_trip(40))
print(card.get_info())Pay 40: True Pay 40: False Guest has 35.5 shekels left.
(ב) את התוכנית הזו כתבנו בסוף פרק 3 במשתנים נפרדים. עכשיו ניצור שני עצמים c1 ו-c2, ונכתוב c1.balance במקום balance1, ו-c1.owner_name במקום name1, והתנאי נשאר כפי שהוא.
(ג) שלושה שינויים: התכונה החדשה trips מתחילה מאפס בפעולה הבונה, גדלה בתוך התנאי כי השאלה סופרת רק נסיעות ששולמו בהצלחה, ו-get_info מוסיפה אותה למחרוזת. נכתוב כאן רק את מה שהשתנה:
class TravelCard:
def __init__(self, owner_name, balance):
self.owner_name = owner_name
self.balance = balance
self.trips = 0
def pay_for_trip(self, cost):
if self.balance >= cost:
self.balance = self.balance - cost
self.trips = self.trips + 1
return True
return False
def get_info(self):
text = self.owner_name + " has "
text = text + str(self.balance)
text = text + " shekels left. Trips: "
return text + str(self.trips)אותו קטע מדפיס בשורה האחרונה: Guest has 35.5 shekels left. Trips: 1. נסיעה אחת, אף שניסינו פעמיים.