تمثيل نوع جديد بواسطة فئة
في الفصل 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
العمليّة مكتوبة في الفئة مرة واحدة، لكنها تعمل على كائنات كثيرة. فكيف تعرف على أي كائن تعمل؟ الجواب هو الوسيط الأول 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__ – העמסת הפעולה __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.
AddNum بحرف كبير في البند (ج)، وفي الشيفرة addNum. بايثون تميّز بينهما، والصحيح هو ما في الشيفرة. وإذا لم تدخل أي علامة صالحة كان 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. سفرة واحدة، مع أننا حاولنا مرتين.