برمجة بايثون 2026 — جامعة هلسنكي › الجزء 4: الدوال والقوائم 4 / 15 السابق التالي

المزيد من الدوال

أهداف التعلم

بعد هذا القسم

  • ستكون على معرفة أكثر بوسائط الدوال ومعاملاتها
  • ستكون قادرًا على إعادة القيم من الدوال، وعلى استخدام القيم في شيفرتك
  • ستكون قادرًا على إضافة تلميحات نوع لمعاملات الدوال وقيم إعادتها

لنستعرض باختصار دوال Python (functions). تُعرَّف الدوال بالكلمة المفتاحية def:

def message():
    print("This message was brought to you by a function")

ويمكن استدعاء هذه الدالة في شيفرة برنامجك هكذا:

message()

وسيطبع برنامجك عندئذٍ

This message was brought to you by a function

معاملات الدالة ووسائطها

يمكن أن تأخذ الدالة وسيطًا واحدًا أو أكثر. وعند استدعاء الدالة، تُسنَد الوسائط إلى متغيّرات تُعرَّف في تعريف الدالة. تُسمّى هذه المتغيّرات المعاملات (parameters)، وتُسرد بين القوسين الواقعين بعد اسم الدالة.

في الشيفرة التالية عُرِّف معامل واحد للدالة greet، بينما عُرِّف معاملان للدالة sum.

def greet(name):
    print("Hello there,", name)

def sum(a, b):
    print("The sum of the arguments is", a + b)
greet("Emily")
sum(2, 3)
Hello there, Emily
The sum of the arguments is 5

الشكلي مقابل الفعلي، المعامل مقابل الوسيط

قد تبدو المصطلحات المستخدمة للبيانات المُمرَّرة إلى الدوال مربكة. وزادةً على ذلك، تشير بعض المصادر إلى ما سمّيناه هنا معاملات ووسائط بمصطلحَي المعامل الشكلي (formal parameter) والمعامل الفعلي (actual parameter). وتطلق عليها مصادر أخرى مصطلحَي الوسيط الشكلي (formal argument) والوسيط الفعلي (actual argument). أما توثيق Python فيحدد مصطلحَي الوسيط (argument) والمعامل (parameter) وحدهما، وذلك ما سنستخدمه نحن أيضًا.

ماذا يحدث فعلًا عند تنفيذ استدعاء الدالة greet("Emily")؟

في تعريف الدالة greet(name) يتصرّف المعامل name من كل النواحي تمامًا كمتغيّر عادي. فيمكننا استخدامه داخل الدالة تمامًا كما استخدمنا المتغيّرات في الدوال الرئيسية المتعددة في برامجنا حتى الآن.

وفي استدعاء الدالة greet("Emily") يكون الوسيط "Emily" كأي سلسلة نصية أخرى صادفناها من قبل. فيمكن مثلًا إسناده إلى متغيّر.

إذن، عند تنفيذ استدعاء الدالة، تُسنَد قيمة الوسيط "Emily" إلى متغيّر المعامل name. وخلال هذا التنفيذ للدالة تكون name = "Emily". وعند استدعاء الدالة بوسيط مختلف، تكون قيمة name مختلفة.

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

رسائل الخطأ عند تشغيل الاختبارات

معظم تمارين هذه الدورة مصحوبة باختبارات أوتوماتيكية. إذا لم يعمل برنامجك كما هو محدَّد في المهمة، فستُظهر الاختبارات رسالة خطأ، وقد تكون مفيدة أو لا. ومن الجدير عادةً أن تُقرأ رسالة الخطأ بتمعّن.

في بعض الحالات قد لا تقول رسالة الخطأ شيئًا يُذكر على الإطلاق. في التمرين التالي أدناه قد تصادف هذه الرسالة:

4_2_1.png

تقول الرسالة فقط إنه يجب أن تكون قادرًا على استدعاء الدالة line بالوسائط المحدَّدة:

line(5, "")

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

4_2_2.png

وتُخبرنا الأسطر الأخيرة الناتجة عن التنفيذ، والمُبرَزة في الصورة أعلاه، أن السطر 4 من شيفرتك يسبّب الخطأ IndexError: string index out of range. وفي الجزء السابق كان هناك مثال مشابه، حيث حاولنا استخدام فهرس يقع خارج نطاق السلسلة النصية. وهذه المرة يظهر الخطأ لأننا نحاول الوصول إلى المحرف الأول من سلسلة نصية فارغة، أي سلسلة طولها 0.

تمرين برمجي: الخط

اكتب دالة اسمها line، تأخذ وسيطين: عددًا صحيحًا وسلسلة نصية. تطبع الدالة سطرًا من النص، طوله محدَّد بالوسيط الأول. والمحرف المستخدم في رسم السطر يجب أن يكون أول محرف في الوسيط الثاني. إذا كان الوسيط الثاني سلسلة نصية فارغة، فيجب أن يتكون السطر من نجوم.

ومثال على السلوك المتوقع:

line(7, "%")
line(10, "LOL")
line(3, "")
%%%%%%%
LLLLLLLLLL
***

استدعاء الدوال داخل الدوال

يمكنك استدعاء دالة من داخل دالة أخرى. وقد فعلنا ذلك مرات كثيرة فعلًا، حين استدعينا الدالة print داخل دوالنا الخاصة في الجزء السابق. ودوالنا الخاصة لا تختلف عنها من الناحية الوظيفية. في المثال التالي تستدعي الدالة greet_many_times الدالة greet بعدد المرات المحدَّد بالوسيط times:

def greet(name):
    print("Hello there,", name)

def greet_many_times(name, times):
    while times > 0:
        greet(name)
        times -= 1

greet_many_times("Emily", 3)
Hello there, Emily
Hello there, Emily
Hello there, Emily

تمرين برمجي: صندوق من محارف الشباك

اكتب دالة اسمها box_of_hashes، تطبع مستطيلًا من محارف الشباك. تأخذ الدالة وسيطًا واحدًا يحدد ارتفاع المستطيل. ويجب أن يكون عرض المستطيل عشرة محارف.

ويجب أن تستدعي الدالة الدالة line من التمرين أعلاه للطباعة الفعلية. انسخ حلّك لذلك التمرين فوق شيفرة هذا التمرين. يُرجى ألا تُغيّر أي شيء في دالتك line.

وبعض الأمثلة على كيفية عمل الدالة:

box_of_hashes(5)
print()
box_of_hashes(2)
##########
##########
##########
##########
##########

##########
##########

تمرين برمجي: مربع من محارف الشباك

اكتب دالة اسمها square_of_hashes، ترسم مربعًا من محارف الشباك. تأخذ الدالة وسيطًا واحدًا يحدد طول ضلع المربع.

ويجب أن تستدعي الدالة الدالة line من التمرين أعلاه للطباعة الفعلية. انسخ حلّك لذلك التمرين فوق شيفرة هذا التمرين. يُرجى ألا تُغيّر أي شيء في دالة line.

وبعض الأمثلة:

square_of_hashes(5)
print()
square_of_hashes(3)
#####
#####
#####
#####
#####

###
###
###

تمرين برمجي: مربع

اكتب دالة اسمها square، تطبع مربعًا من المحارف، وتأخذ وسيطين. يحدد المعامل الأول طول ضلع المربع. ويحدد المعامل الثاني المحرف المستخدم في رسم المربع.

ويجب أن تستدعي الدالة الدالة line من التمرين أعلاه للطباعة الفعلية. انسخ حلّك لذلك التمرين فوق شيفرة هذا التمرين. يُرجى ألا تُغيّر أي شيء في دالة line.

وبعض الأمثلة:

square(5, "*")
print()
square(3, "o")
*****
*****
*****
*****
*****

ooo
ooo
ooo

تمرين برمجي: مثلث

اكتب دالة اسمها triangle، ترسم مثلثًا من محارف الشباك، وتأخذ وسيطًا واحدًا. يجب أن يكون ارتفاع المثلث وعرضه مساويين لقيمة الوسيط.

ويجب أن تستدعي الدالة الدالة line من التمرين أعلاه للطباعة الفعلية. انسخ حلّك لذلك التمرين فوق شيفرة هذا التمرين. يُرجى ألا تُغيّر أي شيء في دالة line.

وبعض الأمثلة:

triangle(6)
print()
triangle(3)
#
##
###
####
#####
######

#
##
###

تمرين برمجي: شكل

اكتب دالة اسمها shape، تأخذ أربعة وسائط. يحدد المعاملان الأولان مثلثًا كما فوق، والمحرف المستخدم في رسمه. ويحدد المعامل الأول أيضًا عرض مستطيل، بينما يحدد المعامل الثالث ارتفاعه. ويحدد المعامل الرابع محرف حشو المستطيل. تطبع الدالة المثلث أولًا، ثم المستطيل أسفله.

ويجب أن تستدعي الدالة الدالة line من التمرين أعلاه للطباعة الفعلية. انسخ حلّك لذلك التمرين فوق شيفرة هذا التمرين. يُرجى ألا تُغيّر أي شيء في دالة line.

وبعض الأمثلة:

shape(5, "X", 3, "*")
print()
shape(2, "o", 4, "+")
print()
shape(3, ".", 0, ",")
X
XX
XXX
XXXX
XXXXX
*****
*****
*****

o
oo
++
++
++
++

.
..
...

تلميح

لا تحاول حل هذا التمرين "دفعة واحدة". فقد تكون الخطوة الأولى الجيدة هي التأكد من قدرتك على طباعة المثلث بشكل موثوق. ثم يمكنك محاولة إضافة المستطيل.

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

تمرين برمجي: شجرة التنوب

اكتب دالة اسمها spruce، تأخذ وسيطًا واحدًا. تطبع الدالة النص a spruce!، ثم شجرة التنوب بحجم تحدده قيمة الوسيط.

ويجب أن يؤدي استدعاء spruce(3) إلى طباعة

a spruce!
  *
 ***
*****
  *

ويجب أن يؤدي استدعاء spruce(5) إلى طباعة

a spruce!
    *
   ***
  *****
 *******
*********
    *

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

القيمة المعادة من الدالة

يمكن للدوال أيضًا أن تُعيد قيمًا. فمثلًا، الدالة المدمجة input في Python تُعيد سلسلة نصية يُدخلها المستخدم. ويمكن تخزين القيمة التي تُعيدها دالة في متغيّر:

word = input("Please type in a word: ")

وعندما تريد قيمة صحيحة من المستخدم، يجب تحويل إدخال المستخدم إلى عدد صحيح. والدالة التي استخدمناها لهذا الغرض هي int، وهي تُعيد قيمة أيضًا:

number = int(input("Please type in an integer: "))

تأخذ الدالة int السلسلة النصية التي تُعيدها input كوسيط لها، وتُعيد قيمة من النوع الصحيح، إذا كان يمكن استخلاص عدد صحيح من السلسلة النصية.

عبارة return

يمكن للدوال التي تُعرّفها بنفسك أيضًا أن تُعيد قيمًا. ولتفعل ذلك تحتاج إلى عبارة return. فمثلًا، تُعيد الدالة my_sum التالية مجموع معامليها:

def my_sum(a, b):
    return a + b

result = my_sum(2, 3)

print("Sum:", result)
Sum: 5

وهناك مثال آخر على القيمة المعادة. تسأل هذه الدالة عن اسم المستخدم وتُعيد السلسلة النصية التي يكتبها:

def ask_for_name():
    name = input("What is your name? ")
    return name

name = ask_for_name()
print("Hello there,", name)
What is your name? **Anna**
Hello there, Anna

وتُنهي عبارة return تنفيذ الدالة فورًا. وهذه طريقة أنيقة لإنشاء دالة مقارنة:

def smallest(a,b):
    if a < b:
        return a
    return b

print(smallest(3, 7))
print(smallest(5, 2))

والفكرة هنا هي أنه إذا كانت a أصغر من b، فإن الدالة تُعيد a وتخرج فورًا. وإلا، يستمر التنفيذ إلى السطر التالي، حيث تُعاد القيمة b. ولا يمكن للدالة أبدًا أن تنفّذ عبارتَي return منفصلتين في استدعاء دالة واحد.

3
2

ويمكنك الاستفادة من عبارة return حتى لو لم تُعِد الدالة قيمة. ويكون هدفها عندئذٍ إنهاء تنفيذ الدالة:

def greet(name):
    if name == "":
        print("???")
        return
    print("Hello there,", name)

greet("Emily")
greet("")
greet("Mark")

إذا كان الوسيط (الذي يُخزَّن في المتغيّر name) سلسلة نصية فارغة، فإن الدالة تطبع ??? وتخرج.

Hello there, Emily
???
Hello there, Mark

استخدام القيم المعادة من الدوال

نعلم بالفعل أنه يمكن تخزين القيم المعادة من الدوال في متغيّرات:

def my_sum(a, b):
    return a + b

result = my_sum(4, 6)
print("The sum is", result)
The sum is 10

والقيمة المعادة من دالة هي قيمة كأي قيمة أخرى. فليس من الضروري تخزينها في متغيّر لكي تُمرَّر كوسيط إلى الأمر print:

print("The sum is", my_sum(4, 6))

ويمكن أن تصبح القيمة المعادة من دالة وسيطًا لدالة أخرى:

def my_sum(a, b):
    return a+b

def difference(a, b):
    return a-b

result = difference(my_sum(5, 2), my_sum(2, 3))
print("The result is", result)
The result is 2

وفي هذه الحالة يُنفَّذ استدعاءا الدالة الداخليان my_sum(5, 2) وmy_sum(2, 3) أولًا. والقيم التي تُعيدها (7 و5) تُستخدم كوسائط لاستدعاء الدالة الخارجية.

ويُعيد استدعاء الدالة الخارجية difference(7, 5) القيمة 2، التي تُخزَّن في المتغيّر result وتُطبع.

وباختصار، تعمل القيم التي تُعيدها الدوال تمامًا كأي قيمة أخرى في Python. فيمكن طباعتها، وتخزينها في متغيّرات، واستخدامها في التعابير، واستخدامها كوسائط في استدعاءات دوال أخرى.

الفرق بين return وprint

قد يكون الفرق بين أن تُعيد الدالة قيمة وأن تحتوي على عبارة طباعة أمرًا محيّرًا أحيانًا. فلنلقِ نظرة على طريقتين مختلفتين لتنفيذ دالة تُحدد أي القيمتين أكبر:

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

def max2(a, b):
    if a > b:
        print(a)
    else:
        print(b)

result = max1(3, 5)
print(result)

max2(7, 2)
5
7

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

max1(3, 5)

لا يبدو أن شيئًا يحدث. فلا بد من استخدام القيمة المعادة من الدالة بطريقة ما في الشيفرة التي استدعت الدالة. فيمكن مثلًا تخزينها في متغيّر وطباعتها:

result = max1(3, 5)
print(result)

أما النسخة الثانية، max2، فتستخدم الأمر print داخل الدالة لطباعة القيمة الأكبر. فإذا أردنا رؤية القيمة، فيكفي استدعاء الدالة

max2(7, 5)

فتُطبَع القيمة الأكبر. والجانب السلبي في هذه الدالة العملية هو أن القيمة التي تحسبها الدالة غير متاحة للاستخدام في البرنامج الذي استدعاها. ولهذا السبب تكون الدوال التي تُعيد قيمًا هي الخيار الأفضل في الغالب.

تمرين برمجي: أكبر عدد

اكتب دالة اسمها greatest_number، تأخذ ثلاثة وسائط. وتُعيد الدالة أكبر القيم الثلاث قيمةً.

ومثال على كيفية استخدام الدالة:

print(greatest_number(3, 4, 1)) # 4
print(greatest_number(99, -4, 7)) # 99
print(greatest_number(0, 0, 0)) # 0

تمرين برمجي: نفس المحارف

اكتب دالة اسمها same_chars، تأخذ سلسلة نصية وعددين صحيحين كوسائط. ويشير العددان الصحيحان إلى فهرسين داخل السلسلة النصية. يجب أن تُعيد الدالة True إذا كان المحرفان الموجودان في الفهرسين المحدَّدين متماثلين. وإلا، وخاصة إذا كان أي من الفهرسين يقع خارج نطاق السلسلة النصية، فتُعيد الدالة False.

وبعض الأمثلة على كيفية استخدام الدالة:

# same characters m and m
print(same_chars("programmer", 6, 7)) # True

# different characters p and r
print(same_chars("programmer", 0, 4)) # False

# the second index is not within the string
print(same_chars("programmer", 0, 12)) # False

تمرين برمجي: الكلمات الأولى والثانية والأخيرة

اكتب ثلاث دوال: first_word وsecond_word وlast_word. تأخذ كل دالة سلسلة نصية كوسيط.

وكما تشير أسماؤها، تُعيد الدوال إما الكلمة الأولى أو الكلمة الثانية أو الكلمة الأخيرة في الجملة التي تتلقاها كوسيط سلسلة نصية.

وفي كل حالة يمكنك أن تفترض أن سلسلة الوسيط تحتوي على كلمتين منفصلتين على الأقل، وأن كل الكلمات مفصولة بمحرف مسافة واحدًا بالضبط. ولن تكون هناك مسافات في بداية السلاسل الوسيطة أو في نهايتها.

sentence = "it was a dark and stormy python"

print(first_word(sentence)) # it
print(second_word(sentence)) # was
print(last_word(sentence)) # python
it
was
python
sentence = "it was"

print(second_word(sentence)) # was
print(last_word(sentence)) # was

نوع الوسيط

واسترجاع سريع لأنواع البيانات التي صادفناها حتى الآن:

النوع نوع البيانات في Python مثال
عدد صحيح int 23
عدد عشري عائم float -0.45
سلسلة نصية str "Peter Python"
قيمة منطقية bool True

وعندما تستدعي دالة، فلن تعمل عملًا صحيحًا إلا إذا كانت الوسائط التي تُمرّرها لها من النوع الصحيح. فلنلقِ نظرة على مثال:

def print_many_times(message, times):
    while times > 0:
        print(message)
        times -= 1

وتعمل الدالة كما هو متوقع إذا استدعيناها هكذا:

print_many_times("Hello there", 5)
Hello there
Hello there
Hello there
Hello there
Hello there

لكن إذا مرّرنا للدالة وسيطًا من النوع الخطأ، فلن تعمل:

print_many_times("Hello there", "Emily")
TypeError: '>' not supported between instances of 'str' and 'int'

والمشكلة هنا هي أن المعامل الثاني times يُقارَن بعدد صحيح، هو 0، في السطر 2 من تعريف الدالة. والوسيط المُمرَّر كان "Emily"، وهو سلسلة نصية وليس عددًا صحيحًا. ولا يمكن مقارنة السلاسل النصية والأعداد الصحيحة بمثل هذه السهولة، فيحدث خطأ.

ولتجنب مشكلات مثل هذه يمكنك إدراج تلميحات النوع في تعريفات دوالك. ويحدد تلميح النوع نوع الوسيط المخصص للدالة:

def print_many_times(message : str, times : int):
    while times > 0:
        print(message)
        times -= 1

وهذا يُخبر أي شخص يستخدم الدالة بأن الوسيط المخزَّن في message من المفترض أن يكون سلسلة نصية، والوسيط المخزَّن في times من المفترض أن يكون عددًا صحيحًا.

وبالمثل، يمكن الإشارة إلى القيمة المعادة من دالة في تعريف الدالة:

def ask_for_name() -> str:
    name = input("Mikä on nimesi? ")
    return name

وهذا يُخبر مستخدم الدالة بأنه من المفترض أن تُعيد الدالة سلسلة نصية.

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