Functions and recursion - Learn Python 3 - Snakify

Lesson 8
وظائف والتكرار


1. المهام

أذكر أنه في الرياضيات يتم تعريف عامل من عدد ن كما ن! = 1 ⋅ 2 ⋅ ... ⋅ n (كمنتج لجميع الأعداد الصحيحة من 1 إلى n). على سبيل المثال ، 5! = 1 ⋅ 2 ⋅ 3 ⋅ 4 ⋅ 5 = 120. من الواضح أنه من السهل حساب عامل ، باستخدام حلقة for. تخيل أننا في برنامجنا نحتاج إلى حساب عدد من الأعداد المختلفة عدة مرات (أو في أماكن مختلفة من الكود). وبالطبع ، يمكنك كتابة حساب العامل المضروب مرة واحدة ثم استخدام نسخ اللصق لإدراجه أينما احتجت:

# حساب 3!
res = 1
for i in range(1, 4):
    res *= i
print(res)

# حساب 5!
res = 1
for i in range(1, 6):
    res *= i
print(res)

ومع ذلك ، إذا ارتكبنا خطأ في الشفرة الأولية ، فسيظهر هذا الرمز الخاطئ في جميع الأماكن التي نسخت فيها حساب العوامل. وعلاوة على ذلك ، فإن الرمز أطول مما يمكن أن يكون. لتجنب إعادة كتابة نفس المنطق في لغات البرمجة هناك وظائف.

الدالات هي المقاطع البرمجية التي يتم عزلها عن باقي البرنامج ويتم تنفيذها فقط عند استدعائها. لقد قابلت بالفعل الدالة sqrt() و len() و print() . لديهم كل شيء مشترك: يمكن أن يأخذوا المعلمات (صفر ، واحد ، أو عدة منهم) ، ويمكنهم إرجاع قيمة (على الرغم من أنهم قد لا يعودوا). على سبيل المثال ، يقبل الدالة sqrt() معلمة واحدة وترجع قيمة (الجذر التربيعي للرقم المحدد). يمكن أن تأخذ الدالة print() عددًا متنوعًا من الوسيطات ولا تعرض أي شيء.

الآن نريد أن نوضح لك كيفية كتابة دالة تسمى factorial() والتي تأخذ معلمة واحدة - العدد ، وترجع قيمة - معامل هذا العدد.

def factorial(n):
    res = 1
    for i in range(1, n + 1):
        res *= i
    return res

print(factorial(3))
print(factorial(5))

نريد أن نقدم بعض التفسيرات. أولاً ، يجب وضع رمز الوظيفة في بداية البرنامج (قبل المكان الذي نريد استخدام الدالة factorial() ، على وجه الدقة). الخط الأول def factorial(n): خط def factorial(n): من هذا المثال هو وصف لوظائفنا ؛ كلمة factorial هي معرف (اسم وظيفتنا). الحق بعد المعرف ، هناك يذهب قائمة المعلمات التي تتلقاها وظيفتنا (بين قوسين). تتكون القائمة من معرّفات مفصولة بفواصل للمعلمات ؛ في حالتنا ، تتكون القائمة من معلمة واحدة n . في نهاية الصف ، ضع القولون.

ثم يذهب الجسم وظيفة. في بايثون ، يجب وضع مسافة بادئة للجسم (بواسطة علامة التبويب أو أربع مسافات ، كما هو الحال دائمًا). هذه الدالة بحساب قيمة ن! ويخزنها في res المتغيرة. السطر الأخير من الدالة هو return res ، الذي يخرج من الدالة ويعيد قيمة res المتغير.

يمكن أن تظهر عبارة return في أي مكان من الوظيفة. يخرج تنفيذه من الوظيفة ويعيد قيمة محددة إلى المكان الذي تم استدعاء الدالة فيه. إذا لم تقم الدالة بإرجاع قيمة ، فلن يقوم بيان الإرجاع بإرجاع بعض القيمة بالفعل (على الرغم من أنه ما زال يمكن استخدامه). بعض الوظائف لا تحتاج إلى إرجاع القيم ، ويمكن حذف بيان الإرجاع لهم.

نود تقديم مثال آخر. إليك الدالة max() التي تقبل رقمين وترجع الحد الأقصى لها (في الواقع ، أصبحت هذه الوظيفة بالفعل جزءًا من بنية Python).

def max(a, b):
    if a > b:
        return a
    else:
        return b

print(max(3, 5))
print(max(5, 3))
print(max(int(input()), int(input())))

الآن يمكنك كتابة max3() وظيفة يأخذ ثلاثة أرقام ويعيد الحد الأقصى منها.

def max(a, b):
    if a > b:
        return a
    else:
        return b

def max3(a, b, c):
    return max(max(a, b), c)

print(max3(3, 5, 4))

يمكن أن تقبل الدالة max() المدمجة في Python عددًا متنوعًا من الوسائط وتقوم بإرجاع الحد الأقصى لها. فيما يلي مثال لكيفية كتابة هذه الوظيفة.

def max(*a):
    res = a[0]
    for val in a[1:]:
        if val > res:
            res = val
    return res

print(max(3, 5, 4))
كل شيء يتم تمريره إلى هذه الوظيفة سيجمع المعلمات إلى مجموعة واحدة تسمى a ، والتي يشار إليها بالعلامة النجمية.
Advertising by Google, may be based on your interests

2. المتغيرات المحلية والعالمية

داخل الوظيفة ، يمكنك استخدام المتغيرات المعلن عنها في مكان ما خارجها:

def f():
    print(a)

a = 1
f()

هنا يتم تعيين المتغير a إلى 1 ، وتقوم الدالة f() بطباعة هذه القيمة ، على الرغم من حقيقة أنه عندما نعلن أن الدالة f لا تتم تهيئة هذا المتغير. والسبب هو، في وقت استدعاء الدالة f() (السلسلة الأخيرة) المتغير a ديها بالفعل قيمة. لهذا السبب يمكن للوظيفة f() عرضها.

وتسمى هذه المتغيرات (المعلن عنها خارج الوظيفة ولكنها متوفرة داخل الدالة) عالميًا .

ولكن إذا قمت بتهيئة بعض المتغيرات داخل الدالة ، فلن تتمكن من استخدام هذا المتغير خارجها. فمثلا:

def f():
    a = 1

f()
print(a)

نتلقى خطأ NameError: name 'a' is not defined . تسمى هذه المتغيرات المعلنة داخل دالة محلية . تصبح غير متوفرة بعد الخروج من الوظيفة.

ما هو ساحر حقاً هنا هو ما يحدث إذا قمت بتغيير قيمة متغير عام داخل دالة:

def f():
    a = 1
    print(a)

a = 0
f()
print(a)

وهذا البرنامج طباعة لكم أرقام 1 و 0. وعلى الرغم من أن قيمة المتغير a تغير داخل وظيفة، وظيفة خارج ذلك لا يزال هو نفسه! يتم ذلك من أجل "حماية" المتغيرات العالمية من التغييرات غير المتعمدة للوظيفة. لذا ، إذا تم تعديل متغير ما داخل الدالة ، يصبح المتغير متغيرًا محليًا ، ولن يغير تعديله متغيرًا عامًا يحمل نفس الاسم.

بشكل أكثر رسمية: المترجم بايثون يعتبر متغير محلي للدالة ، إذا كان في التعليمة البرمجية لهذه الدالة هناك تعليمة واحدة على الأقل تقوم بتعديل قيمة المتغير. ثم لا يمكن استخدام هذا المتغير أيضًا قبل التهيئة. التعليمات التي تعديل قيمة متغير - مشغلي = ، += ، واستخدام المتغير كما حلقة for المعلمة. ومع ذلك ، حتى إذا لم يتم تنفيذ بيان المتغير المتغير ، لا يمكن للمترجم التحقق منه ، وسيظل المتغير محليًا. مثال:

def f():
    print(a)
    if False:
        a = 0

a = 1
f()

يحدث خطأ: UnboundLocalError: local variable 'a' referenced before assignment . وبالتحديد ، في الدالة f() يصبح المعرّف a متغيرًا محليًا ، لأن الدالة تحتوي على الأمر الذي يعدّل المتغير a . لن يتم تنفيذ التعليمات المعدلة مطلقًا ، ولكن لن يتحقق ذلك من المترجم. لذلك ، عند محاولة طباعة المتغير a ، فإنك تروق لمتغير محلي غير مهيأ.

إذا كنت ترغب في أن تكون إحدى الوظائف قادرة على تغيير بعض المتغيرات ، فيجب عليك تعريف هذا المتغير داخل الدالة باستخدام الكلمة الأساسية global :

def f():
    global a
    a = 1
    print(a)

a = 0
f()
print(a)

سيقوم هذا المثال بطباعة إخراج 1 1 ، لأن المتغير a يتم اعتباره عالميًا ، ويؤدي تغييره داخل الدالة إلى تغييره عالميًا.

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

على سبيل المثال ، لنفترض أن البرنامج الخاص بك يجب أن يقوم بحساب عامل العرض الخاص بالرقم المحدد الذي تريد حفظه في المتغير f. إليك كيف يجب عليك عدم القيام بذلك:

def factorial(n):
    global f
    res = 1
    for i in range(2, n + 1):
        res *= i
    f = res

n = int(input())
factorial(n)
print(f)
# القيام بأشياء أخرى مع متغير و

هذا هو مثال الشفرة السيئة ، لأنه من الصعب استخدام وقت آخر. إذا كنت في حاجة غدا إلى برنامج آخر لاستخدام الدالة "factorial" ، فلن تتمكن من نسخ هذه الوظيفة من هنا ولصقها في برنامجك الجديد. سيكون عليك التأكد من أن هذا البرنامج لا يحتوي على المتغير f .

من الأفضل بكثير إعادة كتابة هذا المثال كما يلي:

# بدء جزء من التعليمات البرمجية التي يمكن نسخها من برنامج إلى برنامج
def factorial(n):
    res = 1
    for i in range(2, n + 1):
        res *= i
    return res
# نهاية قطعة من الكود

n = int(input())
f = factorial(n)
print(f)
# القيام بأشياء أخرى مع متغير و

من المفيد أن نقول أن الوظائف يمكن أن تعيد أكثر من قيمة واحدة. إليك مثال إرجاع قائمة بقيمتين أو أكثر:

return [a, b]

يمكنك استدعاء وظيفة هذه القائمة واستخدامها في مهام متعددة:

n, m = f(a, b)
Advertising by Google, may be based on your interests

3. العودية

كما رأينا أعلاه ، يمكن لوظيفة استدعاء دالة أخرى. ولكن يمكن للوظائف أن تطلق على نفسها أيضًا! لتوضيح ذلك ، ضع في اعتبارك مثال وظيفة الحوسبة الحوسبية. من المعروف أن 0! = 1، 1! = 1. كيف تحسب قيمة n! ل كبيرة ن؟ إذا كنا قادرين على حساب قيمة (n-1)! ، فإننا نحسب بسهولة n! ، منذ n! = n⋅ (n-1) !. لكن كيف نحسب (n-1) !؟ إذا قمنا بحساب (n-2)! ، ثم (n-1)! = (n-1) ⋅ (n-2) !. كيف تحسب (ن -2)؟ إذا ... في النهاية ، نصل إلى 0! ، وهو ما يساوي 1. وهكذا ، لحساب عامل يمكن أن نستخدم قيمة عامل لعامل صحيح أصغر. يمكن إجراء هذا الحساب باستخدام Python:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

print(factorial(5))        

يسمى الوضع عند استدعاء الدالة نفسها العودية ، وتسمى هذه الوظيفة العودية.

وظائف متكررة هي آلية قوية في البرمجة. لسوء الحظ ، فهي ليست دائما فعالة وغالبا ما تؤدي إلى أخطاء. الخطأ الأكثر شيوعًا هو العودية اللانهائية ، عندما لا تتوقف مكالمات سلسلة الوظائف أبدًا (في الواقع ، ينتهي الأمر عند نفاد الذاكرة الخالية في الكمبيوتر). مثال على العودية غير المحدودة:

def f():
    return f()
السببان الأكثر شيوعًا وراء التكرار غير المحدود:
  1. شرط التوقف غير صحيح. على سبيل المثال ، إذا كنا ننسى في برنامج حساب المكوّنات ، وضع الشيك if n == 0 ، فإنّ factorial(0) سوف يستدعي factorial(-1) ، الذي سوف يستدعي factorial(-2) ، إلخ.
  2. مكالمة متكررة مع معلمات غير صحيحة. على سبيل المثال ، إذا كانت الدالة factorial(n) تستدعي الدالة factorial(n) ، فسوف نحصل أيضًا على سلسلة غير محدودة من المكالمات.

لذلك ، عند ترميز وظيفة متكررة ، فمن الضروري أولاً التأكد من أنها ستصل إلى ظروف التوقف - للتفكير في سبب انتهاء العودية.

Advertising by Google, may be based on your interests