العمليّات (الدوال)
كلما كبر البرنامج صعبت قراءته: عشرات الأسطر في كتلة واحدة، والحساب نفسه مكتوب في أكثر من مكان، وكل تصحيح يجب أن نتذكر تكراره. في هذا الفصل نتعلم أن نعطي كل مهمة اسما، ونكتبها مرة واحدة، ونستدعيها كلما احتجنا إليها. هذه هي العمليّة، وعليها تقوم الأسئلة الأربعة من امتحانات البجروت التي نحلها في آخر الفصل.
- نقسم مهمة كبيرة إلى عمليّات صغيرة، ونحدد دور كل عمليّة بجملة واحدة.
- نكتب عمليّات تستقبل وسائط، بعضها بقيمة افتراضية.
- نميّز بين عمليّة تعيد قيمة وعمليّة لا تعيد قيمة.
- نتابع الاستدعاء والعودة بجدول متابعة فيه متغيّرات محلية.
- نمرّر قوائم إلى العمليّات ونعيدها منها، ونعرف متى تتغيّر القائمة الأصلية.
- نعرف مدى رؤية كل متغيّر، ونبني برنامجا كاملا حول العمليّة
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) (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 כנקודת הכניסה לקוד התוכנית
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 (כמציין מיקום עבור קוד עתידי) (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