البرمجة بلغة بايثون · الفصل 7 — العمليّات (الدوال)פעולות (פונקציות) · יסודות התכנות בשפת Python⌂📚🏠
الفصل 7

العمليّات (الدوال)

פעולות (פונקציות)
· 20 ساعات تعليمية

كلما كبر البرنامج صعبت قراءته: عشرات الأسطر في كتلة واحدة، والحساب نفسه مكتوب في أكثر من مكان، وكل تصحيح يجب أن نتذكر تكراره. في هذا الفصل نتعلم أن نعطي كل مهمة اسما، ونكتبها مرة واحدة، ونستدعيها كلما احتجنا إليها. هذه هي العمليّة، وعليها تقوم الأسئلة الأربعة من امتحانات البجروت التي نحلها في آخر الفصل.

في هذا الفصل:
  • نقسم مهمة كبيرة إلى عمليّات صغيرة، ونحدد دور كل عمليّة بجملة واحدة.
  • نكتب عمليّات تستقبل وسائط، بعضها بقيمة افتراضية.
  • نميّز بين عمليّة تعيد قيمة وعمليّة لا تعيد قيمة.
  • نتابع الاستدعاء والعودة بجدول متابعة فيه متغيّرات محلية.
  • نمرّر قوائم إلى العمليّات ونعيدها منها، ونعرف متى تتغيّر القائمة الأصلية.
  • نعرف مدى رؤية كل متغيّر، ونبني برنامجا كاملا حول العمليّة 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

المُخرَج هو نفسه تماما. نقرأ التعريف (הגדרה) من اليسار:

الدوال الجاهزة التي نعرفها، مثل len وround، هي عمليّات كتبها مبرمجو بايثون. لكن الفائدة الكبرى ليست في منع التكرار، بل في طريقة التفكير. التفصيل التدريجي (עידון) يعني أن ننظر إلى المهمة الكبيرة، ونقسمها إلى مهام أصغر، ونواصل التقسيم حتى تصبح كل مهمة صغيرة بحيث نعرف كيف نكتبها.

مثال 7.1
يصل إلى المصنع صندوق مقاومات قيمتها الاسمية واحدة. نريد برنامجا يقرأ قياس كل مقاومة، ويفصل المقبولة عن المرفوضة، ويطبع تقريرا. نقسم المهمة قبل أن نكتب سطرا واحدا.
فحص صندوق مقاوماتقراءة القياساتحساب الانحراففصل المرفوضطباعة التقرير
تقسيم المهمة: كل مربع في الصف السفلي عمليّة نكتبها ونفحصها وحدها

سنكتب حساب الانحراف في البند 7.4، وفصل المرفوض في البند 7.8، ونجمع البرنامج كله في البند 7.11.

خطأ شائع: ننسى النقطتين بعد القوسين، أو ننسى القوسين نفسيهما عندما لا يكون هناك وسيط. التعريف def beep(): صحيح، والتعريف def beep: خطأ نحوي.
سؤال 7.1: نريد برنامجا يقرأ قراءات حرارة، ويطبع أعلاها وعدد القراءات فوق 40 درجة. نقترح تقسيما إلى عمليّات: ما اسم كل عمليّة، وماذا تفعل؟

7.2 تحديد دور العمليّة – הגדרת תפקיד הפעולות

قبل أن نكتب الجسم نحدد دور العمليّة، بأن نجيب عن أربعة أسئلة: ما اسمها؟ ماذا تستقبل؟ ماذا تعطي؟ وماذا تفعل بجملة واحدة؟ نجرّب ذلك على دوال نعرفها، وفي السطر الأخير على عمليّة سنكتبها:

العمليّةتستقبلتعطيدورها
len(s)جملة نصية أو قائمةتعيد عددا صحيحاعدد الحروف أو العناصر
print(a, b)قيما من أي نوعتطبع ولا تعيد شيئاعرض القيم على الشاشة
deviation(n, m)عددينتعيد عدداانحراف القياس عن القيمة الاسمية بالنسبة المئوية

ويمكن أن نكتب الدور في أول سطر من الجسم بين علامات اقتباس ثلاثية، وهذه جملة توثيق (מחרוזת תיעוד) لا تؤثر في التنفيذ، وسنرى واحدة في العمليّة deviation في البند 7.4.

التعريف لا ينفّذ شيئا. عندما يصل البرنامج إلى def يحفظ العمليّة باسمها ويتجاوز جسمها. الجسم يعمل عند الاستدعاء فقط، وفي كل استدعاء من جديد. لذلك لم يُطبع شيء عند سطر التعريف، وطُبعت الكلمة beep في الاستدعاءين فقط:

print("start")

def beep():
    print("beep")

beep()
print("end")
beep()
المُخرَج
start
beep
end
beep
ملاحظة: في الامتحان يأتي السؤال بالاتجاه المعاكس: تُعطى عمليّة، ويُطلب أن نكتب «بجملة واحدة» ما الذي تفعله. نجرّبها أولا على مثال أو مثالين، ثم نصف النتيجة لا الأسطر. لا نكتب «تمر على القائمة وتقارن كل عنصر بما قبله»، بل نكتب «تعيد عدد القراءات الأعلى من سابقتها».
خطأ شائع: عمليّة نسميها باسم دالة جاهزة تغطي تلك الدالة: بعد def max(a, b): لا نستطيع أن نستعمل max الجاهزة في البرنامج كله. لذلك نختار اسما آخر.
سؤال 7.2: ماذا يطبع البرنامج؟ ونكتب بجملة واحدة دور العمليّة 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 عمليّات تستقبل وسائط، ومنها وسائط بقيمة افتراضية – פעולות המקבלות פרמטרים, כולל פרמטרים עם ערך ברירת מחדל

الوسيط (פרמטר): متغيّر يُكتب بين قوسي التعريف، ويأخذ قيمته من الاستدعاء. وجسم العمليّة يعمل به مثل أي متغيّر. وهو يقبل قيمة من أي نوع: عددا، أو جملة نصية، أو قيمة بوليانية، أو قائمة كما في البند 7.8.
خطأ شائع: في الاستدعاء نضع قيمة لكل وسيط: عددا، أو متغيّرا، أو تعبيرا يُحسب أولا. وإذا نقصت قيمة توقف البرنامج برسالة تذكر الوسيط الناقص. أما الخطأ الأخطر فهو الترتيب، لأن القيم توزَّع على الوسائط حسب ترتيبها. في الاستدعاء الثاني وصلت القيمتان بترتيب خاطئ، فأصبح الجهد 480 والمقاومة 12، ولم تظهر أي رسالة. ويمكن أن نذكر اسم الوسيط في الاستدعاء، كما في الاستدعاء الثالث، وعندها لا يهم الترتيب:
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.
مثال 7.2
نوصل ثنائيا باعثا للضوء بمصدر جهد عبر مقاومة تحدد تياره. أكثر الثنائيات تعمل عند جهد 2 V وتيار 20 mA، ولذلك نجعلهما قيمتين افتراضيتين، ونذكرهما فقط عندما يختلفان.
الحل:
R = VS − VF I
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).
سؤال 7.3: ماذا يطبع البرنامج التالي؟
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 بعده، كما سنرى في البنود التالية.

مثال 7.3
نعود إلى صندوق المقاومات من البند 7.1. نكتب عمليّة تحسب الانحراف فقط، وهو الفرق المطلق بين القياس والقيمة الاسمية كنسبة مئوية من القيمة الاسمية، وعمليّة ثانية تسأل هل القطعة مقبولة. نسبة السماح 5 بالمئة هي القيمة الافتراضية.
الحل:
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 في تلك الحالة.
سؤال 7.4: المقاومة المكافئة لمقاومتين على التوازي هي حاصل ضربهما مقسوما على مجموعهما. ولأن الاستدعاء يساوي قيمة، نستطيع أن نضعه وسيطا في استدعاء آخر. ماذا يطبع البرنامج التالي؟
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
مثال 7.4
الأمر 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: العمليّة التي تغيّر القائمة لا تعيد شيئا.
سؤال 7.5: ماذا يطبع البرنامج التالي؟
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 استدعاء العمليّات – זימון פעולות

الاستدعاء يقفز بالتنفيذ إلى العمليّة، ثم يعيده إلى المكان الذي خرج منه بالضبط. نتتبع الطريق خطوة بعد خطوة:

البرنامج الذي يستدعيالعمليّةprint("start")a = current(12, 480)print(a)def current(v, r):i = v / rreturn i12341الاستدعاء: يأخذ الوسيطان قيمتيهما، v = 12 و r = 4802يُنفَّذ جسم العمليّة من الأعلى إلى الأسفل3return: نعود إلى السطر نفسه ومعنا القيمة، ويأخذها a4يكمل البرنامج من السطر التالي
مسار الاستدعاء والعودة: السطر الملوّن لا ينتهي إلا بعد العودة

العمليّة تستدعي عمليّة. هذا هو التقسيم الذي تحدثنا عنه، ورأيناه في البند 7.4: is_ok تستدعي deviation، والعودة تصل دائما إلى من استدعى.

مثال 7.5
القيم في المختبر تأتي بوحدات مختلفة: مقاومة بالكيلوأوم، وتيار نريده بالميلي أمبير. نكتب عمليّة تحوّل كل قيمة إلى الوحدة الأساسية، ونستعملها مع قانون أوم. وعندما يكون استدعاء داخل استدعاء، يُحسب الداخلي أولا.
الحل:
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>
سؤال 7.6: ماذا يطبع البرنامج التالي؟ وبأي ترتيب؟
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)
متابعة عمليّة تُستدعى مرتين
الأمرabpower.vpower.ipower.ppower()المُخرَج
a = power(12, 2)
p = v * i12224
return p2424
b = power(5, 3)
p = v * i5315
return p1515
print(a + b)39
مثال 7.6
عمليّة فيها شرط تضع للجدول عمود شرط أيضا. ومع القيمة الافتراضية يظهر الوسيط بقيمته، حتى لو لم نذكره في الاستدعاء. ونلاحظ أن الاستدعاءين خرجا من return مختلف.
الحل:
def limit(v, top=5):
    if v > top:
        return top
    return v

x = limit(7)
y = limit(3)
print(x, y)
الأمرxylimit.vlimit.toplimit()v > topالمُخرَج
x = limit(7)
if v > top:75True
return top55
y = limit(3)
if v > top:35False
return v33
print(x, y)5 3
خطأ شائع: نحمل قيمة من استدعاء إلى استدعاء. المتغيّر المحلي لا يتذكر شيئا: لو كان في العمليّة عدّاد يبدأ من الصفر، لبدأ من الصفر في كل استدعاء.
سؤال 7.7: نبني جدول متابعة للبرنامج التالي، ونكتب المُخرَج.
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]
مثال 7.7
حسبنا في البند 7.3 مقاومة الثنائي الباعث للضوء، لكن المقاومة المحسوبة لا تُباع دائما. نكتب 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]
سؤال 7.8: ماذا يطبع البرنامج التالي؟
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 لأنه لا يستطيع أن يجمع قائمة مع عدد.
سؤال 7.9: ماذا يطبع البرنامج التالي؟
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)

مدى الرؤية (טווח הכרה): الجزء من البرنامج الذي يُعرف فيه المتغيّر.
المتغيّر المحلي (משתנה מקומי): متغيّر يولد داخل عمليّة، ومنه الوسائط. يوجد ما دامت العمليّة تعمل، ولا يُعرف خارجها.
المتغيّر العام (משתנה גלובלי): متغيّر يولد خارج كل العمليّات. يُعرف في البرنامج كله، وتستطيع كل عمليّة أن تقرأه من دون أي إعلان.
مدى الرؤية العامlimit = 11.8def check(v):مدى رؤية محليv = 12.4gap = 0.6def show(v):مدى رؤية محليv = 5gap = 2قراءة فقط
كل عمليّة لها مدى خاص بها: 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)
الأمرiacurrent.icurrent()المُخرَج
i = 9999
a = current(12, 480)
i = v / r0.025
return i0.0250.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
سؤال 7.10: ماذا يطبع البرنامج التالي؟
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
مثال 7.8
نجمع الآن صندوق المقاومات من البند 7.1. كل مربع في التقسيم أصبح عمليّة، و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.
سؤال 7.11: (أ) ماذا يطبع 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 الذي ينتقل إلى الدورة التالية.
سؤال 7.12: (أ) ما الذي يعيده f() إذا كان جسم f هو pass؟
(ب) هل يمكن استعمال pass في جسم if؟
▶تجربة حية — مختبر العمليّاتنشغّل في المختبر برامج فيها عمليّات خطوة بعد خطوة، ونرى كيف يقفز التنفيذ إلى العمليّة ويعود منها، وما قيمة كل متغيّر محلي. تعمل فيه القيم الافتراضية وglobal وpass وإعادة أكثر من قيمة، و* في التعريف والاستدعاء، و__name__. أما input وimport فلا يعرفهما مفسّر المختبر، ولذلك نكتب القيم في المتغيّرات بدل قراءتها، ونضع العمليّات في الملف نفسه بدل استيرادها.
⚠
المختبر غير متاح الآن. المادة في هذا الفصل كاملة من دونه — نواصل القراءة، ونعود إلى التجربة لاحقا أو نفتحها مباشرة من الرابط الذي تحت الإطار.
https://fahed-lab.com/electronics/python_basics#funcs
نفتح المختبر في صفحة مستقلة ↗ fahed-lab.com/electronics/python_basics#funcs
مشروع الكتاب: محطة القياس · المرحلة 7 — المحطة مرتّبة في عمليّات

كتبنا للمحطة حتى الآن برامج منفصلة: تحويل قراءة المحوّل في الفصل 2، والقرار في الفصل 3، وفكّ السطر في الفصل 5. نجمعها الآن في برنامج واحد، وكل منها عمليّة لها دور واحد: adc_to_volts تحوّل قراءة المحوّل، parse_line تفكّ السطر وتفحصه، decide تطبّق قواعد الفصل 3: تفصل الحمل تحت 11.8 V، وتشغّل المروحة فوق 40 درجة والحمل يعمل.

main()adc_to_voltsparse_lineis_numberreportdecide
من يستدعي من في برنامج المحطة
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=46Battery: 11.5 V , Temp: 46.0 C , Load on: False Fan on: False
BAT=12.42;TMP=hotLine rejected
BAT=12.42Line rejected
BAT12.42;TMP=31.5Line 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. أي عمليّة نغيّر؟ وأي عمليّات تبقى كما هي؟

أهمّ أفكار الفصل:

ملخّص أوامر الفصل:

الأمرعمله
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
الإجابات النهائية لأسئلة الفصل
7.1 مثلا: read_temps تقرأ القراءات إلى قائمة، highest تعيد أعلى قراءة، count_above تعيد عدد القراءات فوق الحد. وكل تقسيم معقول صحيح.
7.2 True , False. تطبع True إذا كان في القائمة n عناصر على الأقل قيمتها 1، وإلا False.
7.3 6 , 12 , 7 , 13
7.4 75.0 , 37.5
7.5 3 ok , 7 high , 7 ok , 9 high , None
7.6 twice 1 , twice 3 , plus 2 6 , 8
7.7
الأمرmfix.afix.bfix.cfix()c > 5المُخرَج
m = fix(3, 4)
c = a + b347
if c > 5:True
c = c - 52
return c22
print(m)2
7.8 [1, 2, 3, 4, 5] , [2, 3, 5]
7.9 2 , 2 , 2 , 0
7.10 23 , 1 12
7.11 (أ) __main__ ، (ب) ohm
7.12 (أ) None ، (ب) نعم، في كل مكان يحتاج إلى جسم مُزاح.

أسئلة للتمرين:

المستوى الأول — أساسي
1.
ماذا يطبع البرنامج؟
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 , None
2.
يتوقف البرنامج التالي بخطأ. نجد السبب ونصلحه.
def 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.5
3.
نكتب عمليّة drop تستقبل التيار ثم المقاومة، وتعيد هبوط الجهد على المقاومة. والمقاومة الافتراضية 100 Ω.
الإجابات:
def drop(i, r=100):
    return i * r

print(drop(0.02))
print(drop(0.02, 470))
يطبع: 2.0 , 9.4
4.
نكتب عمليّة تستقبل قائمة قراءات وحدين، وتعيد عدد القراءات التي تقع بين الحدين شاملة إياهما.
الإجابات:
def 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))
يطبع: 2
المستوى الثاني — متوسط
5.
للتأكد من سلامة إطار بيانات، يُرسل معه باقي قسمة مجموع بايتاته على 256. نكتب عمليّتين: checksum تعيد هذا الباقي، و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 , False
6.
نبني جدول متابعة للبرنامج التالي.
def 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)
الإجابات:
الأمرxystep.astep.bstep()المُخرَج
x, y = step(1, 2)
a = a + b32
b = b * 24
return a, b34(3, 4)
x, y = step(x, y)
a = a + b74
b = b * 28
return a, b78(7, 8)
print(x, y)7 8
7.
نكتب عمليّة تستقبل أي عدد من القيم وتعيد أصغرها. نستدعيها مرة بقيم منفصلة، ومرة بقائمة نفكّها.
الإجابات:
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.7
8.
نقرأ قيمة مقاومة من أشرطتها الثلاثة الأولى: الشريطان الأولان رقمان، والثالث قوة العشرة. نكتب عمليّة تستقبل قائمة بأسماء الألوان، وتعيد القيمة بالأوم. نبحث عن كل لون في صفّ ثابت بالعمليّة index.
الإجابات:
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 , 220
المستوى الثالث — تحدٍّ
9.
نكتب عمليّة to_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 , True
10.
نكتب عمليّة network تستقبل نوع الوصل، "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.0
11.
البرنامج التالي يستعمل global. نكتبه من دونه، بوسيط وقيمة مُعادة، ويبقى المُخرَج كما هو.
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 2
12.
نوسّع nearest من البند 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

الأسئلة الأربعة التالية مبنية كلها على العمليّات، ونحلها كاملة. يظهر سؤالا 2024 و2023 بنصهما العربي الرسمي، وسؤالا 2026 و2025 بالعبرية كما في الامتحان، إذ لا يوجد لهما نص عربي رسمي. وكل جدول متابعة هنا ناتج عن تشغيل حقيقي للشيفرة. وفي عناوين الأعمدة كتبنا اسم المتغيّر وحده، كما يطلب السؤال.
بجروت 2026 السؤال 9 — الخانة الأولى والخانة الأخيرة
بجروت 2026
البندالمطلوبالإجابة
(أ)مُخرَج الأوامر الثلاثة.2 , 4 , 6
(ب)قائمة تعيد لها العمليّة 0، وقائمة تعيد لها 5.في الأسفل
(ج)العمليّة sum_of_digits.في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2026

(أ) في عدد من ثلاث خانات، 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
بجروت 2025 السؤال 9 — مقارنة عناصر متقابلة
بجروت 2025
البندالمطلوبالإجابة
(أ)مُخرَج الاستدعاءات الأربعة.HHLL , LHHL , LLHH , HLLH
(ب)قائمتان يطبع لهما البرنامج HHHH.في الأسفل
(ج)العمليّة IsTheSame.في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2025

(أ) هذه عمليّة لا تعيد قيمة: تطبع H عندما يتساوى العنصران في المؤشّر نفسه، وL عندما يختلفان، كلها في سطر واحد بفضل end=""، ثم تنتقل إلى السطر التالي بالأمر print() الأخير. نتابع الاستدعاء الثاني، ونجد الأسطر الأربعة في جدول الإجابات أعلاه:

متابعة DoSomething(lst1, lst3)
الأمرilst1[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
بجروت 2024 السؤال 9 — خانة الآحاد والمكان
بجروت 2024
البندالمطلوبالإجابة
(أ)جدول متابعة للقائمة المعطاة، والقيمة المُعادة.False
(ب)قائمة تعيد لها True وقائمة تعيد لها False.في الأسفل
(ج)دور العمليّة بجملة واحدة.في الأسفل
(د)العمليّة IsInArray.في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2024

(أ) الأمر return False موجود داخل الحلقة، فإذا تحقق الشرط مرة واحدة خرجت العمليّة كلها ولم تكمل القائمة. والسؤال يطلب عمودا لشرط الحلقة أيضا. حلقة for لا شرط مكتوبا لها، لكن سطرها يسأل: هل بقي عنصر نأخذه؟ نسجّل الجواب في العمود for item in lst، ونقرأ في العمود item ما أخذه. والعمود return هو القيمة المُعادة:

متابعة DoSomething(testList)
الأمرiitemreturnfor item in lstitem % 10 != iالمُخرَج
print(DoSomething(testList))
i = 00
for item in lst:20True
if item % 10 != i:False
i = i + 11
for item in lst:41True
if item % 10 != i:False
i = i + 12
for item in lst:2True
if item % 10 != i:False
i = i + 13
for item in lst:93True
if item % 10 != i:False
i = i + 14
for item in lst:14True
if item % 10 != i:False
i = i + 15
for item in lst:894True
if item % 10 != i:True
return FalseFalseFalse

عند العنصر 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
بجروت 2023 السؤال 10 — مقارنة قائمتين
بجروت 2023
البندالمطلوبالإجابة
(أ)جدول متابعة والمُخرَج.True
(ب)تعريف المشكلة التي تحلها العمليّة لقائمتين متساويتين في الكبر.في الأسفل
(ج)العمليّة countEven (في النص العربي CountEven).في الأسفل
الحل الكامل بالعربية: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2023

(أ) نضيف إلى الشيفرة السطر main_10() الذي يشغّلها. القائمتان متساويتان في الطول، فنصل إلى else، حيث يجمع s الفروق بين العناصر المتقابلة. لا نكتب عمودا للقائمتين لأن السؤال لا يطلب ذلك، ولا يتغيّر فيهما شيء:

الأمرibxyssod()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 = TrueTrue
if len(arr1) != len(arr2):False
s = 00
i = 00
for x in arr1:10
y = arr2[i]10
s = s + x - y
i = i + 11
for x in arr1:15
y = arr2[i]
s = s + x - y5
i = i + 12
for x in arr1:
y = arr2[i]
s = s + x - y10
i = i + 13
for x in arr1:5
y = arr2[i]
s = s + x - y5
i = i + 14
for x in arr1:
y = arr2[i]
s = s + x - y0
i = i + 15
for x in arr1:
if s != 0:False
return bTrueTrue

في النهاية 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