الفصل السادس: الدوال – بناء وحدات برمجية قابلة لإعادة الاستخدام
مقدمة الفصل
عندما تبدأ بكتابة برامج أكبر، ستلاحظ أنك قد تكرر نفس الأوامر في أماكن مختلفة. هذا التكرار يجعل الكود طويلاً، صعب القراءة، وصعب الصيانة. مبدأ “لا تكرر نفسك” (Don’t Repeat Yourself - DRY) هو أحد أهم المبادئ في البرمجة.
الدوال (Functions) هي الأداة الأساسية لتطبيق هذا المبدأ. الدالة هي كتلة من الكود المنظم والقابل لإعادة الاستخدام، مصممة لأداء مهمة محددة. يمكنك التفكير فيها كـ “وصفة” أو “برنامج مصغر” يمكنك استدعاؤه باسمه في أي وقت لتنفيذ مهمته.
1. ما هي الدالة وكيف نعرّفها؟
لتعريف دالة في بايثون، نستخدم الكلمة المفتاحية def
.
البنية الأساسية:
def function_name():
# Code block to be executed
# This block must be indented
print("This is a function.")
def
: الكلمة التي تخبر بايثون أنك على وشك تعريف دالة.function_name
: الاسم الذي تختاره للدالة (يتبع نفس قواعد تسمية المتغيرات).()
: الأقواس ضرورية، وسنرى لاحقًا أنها يمكن أن تحتوي على “معاملات”.:
: النقطتان الرأسيتان اللتان تنهيان سطر التعريف وتبدآن الكتلة البرمجية.- الكتلة البرمجية (Code Block): كل الأسطر التي تلي التعريف بمسافة بادئة هي جزء من الدالة.
توثيق دوالك: سلاسل التوثيق (Docstrings)
من أفضل الممارسات البرمجية توثيق ما تفعله دوالك. الطريقة الرسمية لفعل ذلك في بايثون هي استخدام سلسلة التوثيق (docstring). وهي عبارة عن سلسلة نصية متعددة الأسطر توضع مباشرة بعد سطر تعريف الدالة.
def greet():
"""Prints a simple welcome message to the screen."""
print("Welcome to Python!")
لماذا هي مهمة؟ لأنها تسمح لأي شخص (بمن فيهم أنت في المستقبل) بفهم وظيفة الدالة بسرعة. كما أن العديد من أدوات البرمجة تستخدمها لعرض المساعدة تلقائيًا.
استدعاء الدالة (Calling the Function)
تعريف الدالة يشبه كتابة وصفة طعام، لكنها لن تُنفذ حتى تقرر “طبخها”. لجعل الدالة تعمل، يجب عليك “استدعاؤها” باسمها متبوعًا بالأقواس.
# أولاً، نعرّف الدالة
def greet():
"""Prints a simple welcome message to the screen."""
print("Welcome to Python!")
# الآن، نستدعي الدالة لتنفيذ الكود الذي بداخلها
greet()
greet() # يمكننا استدعاؤها عدة مرات
2. تمرير البيانات إلى الدوال (المعاملات والوسائط)
لجعل الدوال أكثر قوة وفائدة، يمكننا تمرير بيانات إليها لتعمل عليها.
- المعامل (Parameter): هو المتغير الذي يُكتب كـ “عنصر نائب” داخل الأقواس عند تعريف الدالة. إنه يمثل المعلومة التي تتوقعها الدالة.
- الوسيط (Argument): هو القيمة الفعلية التي تُمرر إلى الدالة عند استدعائها.
توضيح بالتشبيه: فكر في المعامل (Parameter) كخانة فارغة في وصفة مكتوب عليها “مقدار السكر”. أما الوسيط (Argument) فهو القيمة الحقيقية التي تستخدمها عند الطبخ، مثل “كوبان من السكر”.
# 'name' هنا هو المعامل (Parameter)
def greet_user(name):
"""Prints a personalized greeting."""
print(f"Hello, {name}!")
# "Ahmed" و "Sara" هما الوسيطان (Arguments)
greet_user("Ahmed")
greet_user("Sara")
الوسائط المفتاحية (Keyword Arguments)
عادةً، تمرر الوسائط بالترتيب (تسمى وسائط موضعية). لكن بايثون تسمح لك بتمريرها عن طريق تحديد اسم المعامل، مما يجعل الترتيب غير مهم.
def user_info(name, age):
"""Prints user's name and age."""
print(f"User: {name}, Age: {age}")
# باستخدام الوسائط المفتاحية، الترتيب لم يعد مهمًا
user_info(age=30, name="Ali")
الفائدة: هذا يجعل الكود أكثر وضوحًا وقراءة، خاصة في الدوال التي تحتوي على العديد من المعاملات.
3. إعادة القيم من الدوال باستخدام return
في كثير من الأحيان، لا نريد أن تقوم الدالة بالطباعة فقط، بل نريدها أن تحسب نتيجة وتعيدها لنا لنستخدمها في بقية البرنامج. لهذا نستخدم الكلمة المفتاحية return
.
الفرق الجوهري بين print
و return
:
print()
: تعرض القيمة للمستخدم على الشاشة. إنها وظيفة إخراجية فقط ولا تؤثر على سير الكود.return
: تعيد القيمة إلى الكود الذي استدعى الدالة، مما يسمح لك بتخزينها في متغير أو استخدامها في عمليات أخرى.
# هذه الدالة تحسب المجموع وتعيده
def add_numbers(num1, num2):
"""Takes two numbers and returns their sum."""
result = num1 + num2
return result
# نستدعي الدالة ونخزن قيمتها المرتجعة في متغير
sum_result = add_numbers(10, 5)
print(f"The result is: {sum_result}") # نطبع النتيجة التي تم إرجاعها
print(f"We can use it again: {sum_result * 2}") # يمكننا استخدامها في عمليات أخرى
ماذا لو لم تستخدم return
؟
إذا انتهت الدالة دون أن تصل إلى سطر return
، فإنها تعيد ضمنيًا قيمة خاصة تسمى None
. None
في بايثون تمثل “العدم” أو “لا قيمة”.
def say_hello(name):
print(f"Hello, {name}")
result = say_hello("Mona")
print(result)
# الناتج سيكون:
# Hello, Mona
# None
4. المعاملات ذات القيمة الافتراضية
يمكنك جعل معامل الدالة اختياريًا عن طريق تزويده بقيمة افتراضية. إذا لم يمرر المستخدم وسيطًا لهذا المعامل عند الاستدعاء، فسيتم استخدام القيمة الافتراضية.
# 'country' له قيمة افتراضية هي "Unknown"
def user_profile(name, country="Unknown"):
"""Prints a user profile."""
print(f"Name: {name}, Country: {country}")
user_profile("Fatima") # سيتم استخدام القيمة الافتراضية لـ country
user_profile("John", "USA") # سيتم استخدام القيمة الممررة "USA"
5. نطاق المتغيرات (Variable Scope)
هذا مفهوم مهم جدًا لفهم كيفية عمل المتغيرات. أي متغير يتم إنشاؤه داخل دالة يُعرف بأنه متغير محلي (local variable). هذا يعني أنه “يعيش” فقط داخل تلك الدالة ولا يمكن الوصول إليه أو رؤيته من خارجها.
def my_function():
# 'x' هو متغير محلي، يوجد فقط داخل my_function
x = 100
print(f"Inside the function, x is: {x}")
my_function()
# السطر التالي سيسبب خطأ NameError لأن 'x' غير معروف هنا في الخارج
# print(f"Outside the function, x is: {x}")
المتغيرات التي يتم تعريفها خارج أي دالة تسمى متغيرات عامة (global variables) ويمكن قراءتها من أي مكان في الكود.
هذا التصميم مفيد جدًا لأنه يمنع الدوال المختلفة من تعديل متغيرات بعضها البعض عن طريق الخطأ، مما يجعل برامجك أكثر أمانًا وموثوقية.
⚠️ تحذير متقدم: تجنب تعديل المتغيرات العامة من داخل الدوال قدر الإمكان. بايثون توفر كلمة
global
للسماح بذلك، لكن استخدامها يجعل تتبع الأخطاء صعبًا. الممارسة الأفضل دائمًا هي أن تتواصل الدوال عبر المعاملات وقيمreturn
.
6. تمرين تطبيقي: آلة حاسبة منظمة
الآن، لنطبق ما تعلمناه لبناء آلة حاسبة بسيطة ومنظمة باستخدام الدوال.
المطلوب:
بناء آلة حاسبة للعمليات الأساسية (الجمع، الطرح، الضرب، القسمة).
إرشادات الحل:
- فكك المشكلة: أنشئ دالة منفصلة لكل عملية حسابية (
add
,subtract
,multiply
,divide
). - المدخلات والمخرجات: كل دالة يجب أن تأخذ رقمين كـ معاملات وتعيد (
return
) ناتج العملية. - الحالات الخاصة: في دالة القسمة، تحقق من حالة القسمة على صفر.
- الجزء الرئيسي: اعرض قائمة، اطلب من المستخدم اختيار عملية ورقمين، ثم استدعِ الدالة المناسبة واطبع النتيجة.
الحل المقترح:
def add(x, y):
"""Returns the sum of two numbers."""
return x + y
def subtract(x, y):
"""Returns the difference of two numbers."""
return x - y
def multiply(x, y):
"""Returns the product of two numbers."""
return x * y
def divide(x, y):
"""Returns the division of two numbers, handles division by zero."""
if y == 0:
return "Error: Cannot divide by zero."
return x / y
# --- الجزء الرئيسي من البرنامج ---
print("Select an operation:")
print("1. Add")
print("2. Subtract")
print("3. Multiply")
print("4. Divide")
choice = input("Enter choice (1/2/3/4): ")
# ملاحظة: هذا الكود يفترض أن المستخدم سيدخل أرقامًا.
# سنتعلم كيفية معالجة المدخلات الخاطئة بشكل احترافي في فصول قادمة.
num1_str = input("Enter first number: ")
num2_str = input("Enter second number: ")
# تحويل المدخلات إلى أرقام
num1 = float(num1_str)
num2 = float(num2_str)
# استدعاء الدالة المناسبة بناءً على اختيار المستخدم
if choice == '1':
result = add(num1, num2)
print(f"{num1} + {num2} = {result}")
elif choice == '2':
result = subtract(num1, num2)
print(f"{num1} - {num2} = {result}")
elif choice == '3':
result = multiply(num1, num2)
print(f"{num1} * {num2} = {result}")
elif choice == '4':
result = divide(num1, num2)
print(f"{num1} / {num2} = {result}")
else:
print("Invalid choice.")
تمارين تطبيقية
التمرين 1: دالة الترحيب مع التوثيق
المطلوب:
اكتب دالة باسم display_greeting
. يجب ألا تأخذ الدالة أي معاملات. عند استدعائها، يجب أن تطبع السطر التالي: “مرحبًا بك في عالم الدوال في بايثون!”. الأهم من ذلك، يجب أن تحتوي الدالة على سلسلة توثيق (docstring) تشرح وظيفتها.
الحل:
def display_greeting():
"""
Prints a static welcome message to the screen.
This function takes no parameters.
"""
print("مرحبًا بك في عالم الدوال في بايثون!")
# استدعاء الدالة لتجربتها
display_greeting()
التمرين 2: محول درجة الحرارة
المطلوب:
اكتب دالة باسم celsius_to_fahrenheit
تأخذ درجة حرارة بالدرجة المئوية (Celsius) كمعامل. يجب أن تقوم الدالة بحساب الدرجة المعادلة بالفهرنهايت وتعيد (return
) القيمة المحسوبة.
المعادلة: $Fahrenheit = (Celsius \times \frac{9}{5}) + 32$
الحل:
def celsius_to_fahrenheit(celsius_temp):
"""Converts a temperature from Celsius to Fahrenheit and returns the result."""
fahrenheit_temp = (celsius_temp * 9/5) + 32
return fahrenheit_temp
# تجربة الدالة
temp_c = 25
temp_f = celsius_to_fahrenheit(temp_c)
print(f"{temp_c} درجة مئوية تساوي {temp_f} درجة فهرنهايت.")
التمرين 3: وصف الملف الشخصي بالوسائط المفتاحية
المطلوب:
اكتب دالة describe_profile
تأخذ ثلاثة معاملات: username
, followers
, posts
. يجب أن تطبع الدالة ملخصًا للملف الشخصي. ثم قم باستدعاء الدالة باستخدام الوسائط المفتاحية (Keyword Arguments) وبترتيب مختلف عن ترتيب تعريف المعاملات.
الحل:
def describe_profile(username, followers, posts):
"""Prints a summary of a user's profile."""
print(f"--- Profile Summary ---")
print(f"Username: @{username}")
print(f"Followers: {followers}")
print(f"Total Posts: {posts}")
# استدعاء الدالة باستخدام الوسائط المفتاحية وبترتيب مختلف
describe_profile(posts=150, username="py_dev", followers=4200)
التمرين 4: دالة بقيمة افتراضية
المطلوب:
اكتب دالة calculate_tax
تأخذ معاملين: price
(السعر) و tax_rate
(معدل الضريبة). يجب أن يكون للمعامل tax_rate
قيمة افتراضية تساوي 0.14
. يجب أن تحسب الدالة قيمة الضريبة النهائية (price * tax_rate
) وتعيدها.
الحل:
def calculate_tax(price, tax_rate=0.14):
"""Calculates the tax amount for a given price and tax rate."""
return price * tax_rate
# استدعاء الدالة بدون تحديد معدل الضريبة (سيستخدم القيمة الافتراضية 0.14)
tax_on_100 = calculate_tax(100)
print(f"الضريبة على سعر 100 (بالمعدل الافتراضي) هي: {tax_on_100}")
# استدعاء الدالة مع تحديد معدل ضريبة مختلف (0.20)
tax_on_100_special = calculate_tax(100, 0.20)
print(f"الضريبة على سعر 100 (بمعدل 20%) هي: {tax_on_100_special}")
التمرين 5: التنبؤ بنتيجة النطاق
المطلوب: ما الذي سيتم طباعته عند تشغيل الكود التالي؟ اشرح السبب.
x = 50 # متغير عام
def update_value():
x = 10 # متغير محلي
print(f"داخل الدالة، x تساوي {x}")
update_value()
print(f"خارج الدالة، x تساوي {x}")
الحل: سيتم طباعة ما يلي:
داخل الدالة، x تساوي 10
خارج الدالة، x تساوي 50
السبب: المتغير x
الذي تم إنشاؤه داخل دالة update_value
هو متغير محلي ومنفصل تمامًا عن المتغير x
المعرّف في الخارج (النطاق العام). تعديل المتغير المحلي لا يؤثر أبدًا على المتغير العام الذي يحمل نفس الاسم.
التمرين 6: قيمة None
المرتجعة
المطلوب: ما الذي سيتم طباعته عند تشغيل الكود التالي؟
def log_message(message):
print(f"LOG: {message}")
return_value = log_message("System starting...")
print(f"القيمة المرتجعة من الدالة هي: {return_value}")
الحل: سيتم طباعة ما يلي:
LOG: System starting...
القيمة المرتجعة من الدالة هي: None
السبب: دالة log_message
تقوم بالطباعة على الشاشة ولكنها لا تحتوي على سطر return
. في بايثون، أي دالة لا تعيد قيمة بشكل صريح، فإنها تعيد القيمة الخاصة None
بشكل ضمني.
التمرين 7: عداد الحروف المتحركة
المطلوب:
اكتب دالة count_vowels
تأخذ نصًا (string
) كمعامل، وتقوم بحساب عدد الحروف المتحركة (a, e, i, o, u) في هذا النص، ثم تعيد العدد النهائي. يجب أن تكون الدالة غير حساسة لحالة الأحرف.
الحل:
def count_vowels(text):
"""Counts the number of vowels in a given text (case-insensitive)."""
vowels = "aeiou"
vowel_count = 0
# المرور على كل حرف في النص بعد تحويله إلى أحرف صغيرة
for char in text.lower():
# إذا كان الحرف موجودًا في سلسلة الحروف المتحركة، زد العداد
if char in vowels:
vowel_count += 1 # نفس معنى vowel_count = vowel_count + 1
return vowel_count
# تجربة الدالة
sentence = "This is a Test Sentence"
num_vowels = count_vowels(sentence)
print(f"الجملة '{sentence}' تحتوي على {num_vowels} حرفًا متحركًا.")
التمرين 8: التحقق من الرقم الزوجي
المطلوب:
اكتب دالة is_even
تأخذ رقمًا كمعامل. إذا كان الرقم زوجيًا، يجب أن تعيد الدالة القيمة المنطقية True
. إذا كان فرديًا، يجب أن تعيد False
.
الحل:
def is_even(number):
"""Checks if a number is even. Returns True if even, False otherwise."""
# نستخدم باقي القسمة للتحقق. إذا كان باقي القسمة على 2 هو 0، فالرقم زوجي.
return number % 2 == 0
# تجربة الدالة
print(f"هل الرقم 10 زوجي؟ {is_even(10)}")
print(f"هل الرقم 7 زوجي؟ {is_even(7)}")
التمرين 9: دالتان معًا
المطلوب: اكتب برنامجًا يحسب حجم صندوق على شكل متوازي مستطيلات.
- اكتب دالة أولى
calculate_base_area
تأخذ الطول (length
) والعرض (width
) وتعيد مساحة القاعدة. - اكتب دالة ثانية
calculate_volume
تأخذ مساحة القاعدة (base_area
) والارتفاع (height
) وتعيد الحجم النهائي. - في الجزء الرئيسي من البرنامج، اطلب من المستخدم إدخال الطول والعرض والارتفاع، ثم استخدم الدالتين لحساب وعرض الحجم النهائي.
الحل:
def calculate_base_area(length, width):
"""Calculates the area of a rectangle."""
return length * width
def calculate_volume(base_area, height):
"""Calculates the volume based on base area and height."""
return base_area * height
# --- الجزء الرئيسي من البرنامج ---
print("--- حاسبة حجم الصندوق ---")
user_length = float(input("أدخل طول الصندوق: "))
user_width = float(input("أدخل عرض الصندوق: "))
user_height = float(input("أدخل ارتفاع الصندوق: "))
# استدعاء الدالة الأولى للحصول على مساحة القاعدة
area = calculate_base_area(user_length, user_width)
# استخدام نتيجة الدالة الأولى كوسيط للدالة الثانية
volume = calculate_volume(area, user_height)
print(f"حجم الصندوق هو: {volume} وحدة مكعبة.")
التمرين 10: مدقق قوة كلمة المرور
المطلوب:
اكتب دالة is_password_strong
تأخذ كلمة مرور (نص) كمعامل وتتحقق من الشروط التالية:
- يجب ألا يقل طولها عن 8 أحرف.
- يجب أن تحتوي على رقم واحد على الأقل.
إذا تحققت كل الشروط، يجب أن تعيد الدالة True
. وإلا، يجب أن تعيد False
.
الحل:
def is_password_strong(password):
"""
Checks if a password meets two conditions:
1. At least 8 characters long.
2. Contains at least one digit.
Returns True if strong, False otherwise.
"""
# الشرط الأول: التحقق من الطول
length_ok = len(password) >= 8
# الشرط الثاني: التحقق من وجود رقم
has_digit = False
for char in password:
if char.isdigit():
has_digit = True
break # وجدنا رقمًا، لا داعي لإكمال الحلقة
# تعيد الدالة True فقط إذا تحقق الشرطان معًا
return length_ok and has_digit
# تجربة الدالة
pass1 = "password123"
pass2 = "weak"
pass3 = "longpassword"
print(f"هل كلمة المرور '{pass1}' قوية؟ {is_password_strong(pass1)}")
print(f"هل كلمة المرور '{pass2}' قوية؟ {is_password_strong(pass2)}")
print(f"هل كلمة المرور '{pass3}' قوية؟ {is_password_strong(pass3)}")
7. خلاصة الفصل
- الدوال تنظم الكود وتجعله قابلاً لإعادة الاستخدام، مما يطبق مبدأ DRY.
- نستخدم
def
لتعريف دالة وسلاسل التوثيق (docstrings) لشرح وظيفتها. - المعاملات (Parameters) تجعل الدوال مرنة، ويمكن تمرير الوسائط (Arguments) إليها بالترتيب أو باستخدام الوسائط المفتاحية (Keyword Arguments).
return
تسمح للدوال بإعادة قيمة يمكن استخدامها في أجزاء أخرى من البرنامج. إذا لم تُستخدم، تعيد الدالةNone
.- المتغيرات المحلية (Local Scope) تضمن بقاء المتغيرات معزولة داخل دوالها، مما يمنع التضارب ويجعل الكود أكثر أمانًا.
ماذا بعد؟
الآن بعد أن أتقنت تنظيم المنطق البرمجي في وحدات، حان الوقت لنتعلم كيفية تنظيم البيانات نفسها. في الفصل السابع، سنغوص في عالم هياكل البيانات الأساسية في بايثون: القوائم، المجموعات، والقواميس.