المعاملات المتقدمة والنطاق
الآن بعد أن تعلمت أساسيات الدوال، دعنا نستكشف بعض التقنيات المتقدمة. كم طريقة يمكنك كتابة المعاملات بها؟ ماذا يعني "نطاق المتغير"؟ ما هي العودية؟ هذه المفاهيم سترفع فهمك للدوال إلى المستوى التالي.
1. المعاملات الافتراضية
المعاملات الافتراضية تتيح لك حذف الوسائط عند استدعاء دالة:
مثال: المعاملات الافتراضية الأساسية
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
greet("Charlie", "Hey") # Hey, Charlie!
⚠️ فخ المعامل الافتراضي
قيم المعاملات الافتراضية تُقيّم مرة واحدة فقط، عند تعريف الدالة، وليس في كل مرة تُستدعى فيها الدالة. إذا كان الافتراضي كائناً قابلاً للتعديل (مثل قائمة أو قاموس)، يمكن أن يؤدي هذا إلى سلوك غير متوقع:
مثال: فخ المعامل الافتراضي القابل للتعديل
def add_item(item, items=[]): # ❌ خطير!
items.append(item)
return items
print(add_item("apple")) # ['apple']
print(add_item("banana")) # ['apple', 'banana'] — وليس ['banana']!
print(add_item("orange")) # ['apple', 'banana', 'orange']
لماذا؟ القائمة الفارغة items=[] تُنشأ مرة واحدة عند تعريف الدالة. كل استدعاء لاحق يُضيف إلى نفس القائمة.
الطريقة الصحيحة: استخدم None كافتراضي وأنشئ قائمة جديدة داخل الدالة:
مثال: معامل افتراضي آمن
def add_item(item, items=None): # ✅ آمن
if items is None:
items = []
items.append(item)
return items
print(add_item("apple")) # ['apple']
print(add_item("banana")) # ['banana']
print(add_item("orange")) # ['orange']
None، أرقام، سلاسل نصية، صفوف). لا تستخدم أبداً كائنات قابلة للتعديل مثل القوائم أو القواميس كمعاملات افتراضية.
2. الوسائط المفتاحية
عند استدعاء دالة، يمكنك تحديد القيم باسم المعامل، مما يسمح بتمرير الوسائط خارج الترتيب:
مثال: الوسائط المفتاحية
def create_user(name, age, city):
print(f"User: {name}, {age} years old, from {city}")
# الوسائط الموضعية — يجب أن تكون بالترتيب
create_user("Alice", 25, "Beijing")
# الوسائط المفتاحية — حدد أسماء المعاملات، الترتيب لا يهم
create_user(age=30, city="Shanghai", name="Bob")
create_user(name="Charlie", city="Guangzhou", age=22)
# مختلطة — موضعية أولاً، مفتاحية ثانياً
create_user("Diana", city="Shenzhen", age=28)
# create_user(name="Eve", 35, "Chengdu") ❌ الوسائط المفتاحية لا يمكن أن تسبق الموضعية
3. *args: وسائط موضعية متغيرة
إذا كنت لا تعرف عدد الوسائط التي ستستقبلها الدالة، استخدم *args:
مثال: *args لدالة الجمع
def sum_all(*numbers):
"""تقبل أي عدد من الأرقام وتُرجع مجموعها"""
total = 0
for n in numbers:
total += n
return total
print(sum_all(1, 2)) # 3
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0 — بدون وسائط أيضاً تعمل
*args يحزم جميع الوسائط الموضعية في صف:
مثال: فحص args
def show_args(*args):
print(f"Received {len(args)} arguments: {args}")
for i, arg in enumerate(args, 1):
print(f" #{i}: {arg}")
show_args("apple", "banana", "orange")
4. **kwargs: وسائط مفتاحية متغيرة
**kwargs تقبل أي عدد من الوسائط المفتاحية وتحزمها في قاموس:
مثال: **kwargs لبناء ملفات تعريف المستخدمين
def create_profile(**info):
"""أنشئ ملف تعريف مستخدم، يقبل أي قدر من المعلومات"""
print("User Profile:")
for key, value in info.items():
print(f" {key}: {value}")
create_profile(name="Alice", age=25, city="Beijing", job="Engineer")
المخرجات:
User Profile:
name: Alice
age: 25
city: Beijing
job: Engineer
مثال: دمج *args و **kwargs
def advanced_function(a, b, *args, **kwargs):
print(f"Positional: a={a}, b={b}")
print(f"Extra positional: {args}")
print(f"Keyword: {kwargs}")
advanced_function(1, 2, 3, 4, 5, name="Alice", age=25)
# Positional: a=1, b=2
# Extra positional: (3, 4, 5)
# Keyword: {'name': 'Alice', 'age': 25}
المعاملات العادية → *args → المعاملات الافتراضية → **kwargs
5. النطاق: قاعدة LEGB
أين يمكن الوصول إلى متغير؟ هذا يحدده النطاق. لدى بايثون أربعة مستويات من النطاق:
مثال: تداخل نطاق LEGB
# نطاق عالمي
x = 10 # متغير عالمي
def outer():
# نطاق الدالة المحيطة
x = 20 # هذا هو x المحلي لـ outer، مختلف عن x العالمي
def inner():
# نطاق محلي
x = 30 # هذا هو x المحلي لـ inner
print(f"inner: {x}") # 30
inner()
print(f"outer: {x}") # 20
outer()
print(f"global: {x}") # 10
LEGB تعني:
- Local — داخل الدالة الحالية
- Enclosing — متغيرات الدالة المحيطة
- Global — على مستوى الوحدة
- Built-in — دوال بايثون المدمجة (مثل
print,len)
تبحث بايثون عن المتغيرات بترتيب L → E → G → B.
global و nonlocal
لتعديل متغير عالمي داخل دالة، استخدم global:
مثال: استخدام global
count = 0
def increment():
global count # أعلن عن نية تعديل المتغير العالمي
count += 1
increment()
increment()
print(count) # 2
global. المتغيرات العالمية تجعل الكود أصعب في الفهم والتصحيح. يجب أن تستقبل الدوال المدخلات من خلال المعاملات وتُخرج النتائج من خلال return، وليس تعديل المتغيرات العالمية سراً.
6. أساسيات العودية
العودية هي عندما تستدعي دالة نفسها. الفكرة الأساسية: قسم مشكلة كبيرة إلى مشكلات فرعية أصغر ومتشابهة.
مثال: العودية لحساب المضروب
def factorial(n):
"""احسب n! (مضروب n)"""
if n <= 1:
return 1 # شرط الإنهاء
return n * factorial(n - 1) # استدعاء عودي
print(factorial(5)) # 120 (5×4×3×2×1)
التنفيذ:
factorial(5) = 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
مثال: متتالية فيبوناتشي (الصعوبة ⭐⭐⭐)
def fibonacci(n):
"""أرجع رقم فيبوناتشي رقم n (بدءاً من 0)"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# اطبع أول 10
for i in range(10):
print(fibonacci(i), end=" ")
# يُخرج: 0 1 1 2 3 5 8 13 21 34
حالات الاستخدام الشائعة
- المعاملات الافتراضية: خيارات التهيئة، قيم المهلة، دوال الاستدعاء العكسي.
*args: دوال التجميع الرياضي (الجمع، المتوسط)، تنسيق السجلات.**kwargs: أغلفة الدوال، دمج التهيئة، معاملات استعلام قاعدة البيانات.- العودية: اجتياز شجرة الدليل، تحليل JSON المتداخل، الصيغ الرياضية (المضروب/فيبوناتشي).
- النطاق: فهم الإغلاقات (الدرس التالي)، فهم عمر المتغير.
❓ أسئلة شائعة
*args و kwargs؟*args و kwargs مجرد اصطلاحات تسمية — العلامات النجمية هي المهمة. يمكنك كتابة *numbers، **options. لكن اتباع الاصطلاح موصى به حتى يفهمه مبرمجو بايثون الآخرون فوراً.
⚠️ س:العودية مقابل الحلقات — أيهما أفضل؟ ج:إذا كانت الحلقة يمكنها حل المشكلة، فضّل الحلقة. الحلقات أداء أفضل، ولن تسبب فيضاً في المكدس، وأسهل في الفهم. العودية مناسبة لسيناريوهين: اجتياز الهياكل الشجرية (الدلائل، JSON) والتعريفات الرياضية العودية (المضروب، فيبوناتشي). العودية أكثر إيجازاً لكنها أصعب في الفهم.
س:لماذا يمكنني قراءة متغير عالمي داخل دالة لكن لا يمكنني تعديله؟ ج:عند القراءة، تجد بايثون المتغير العالمي باتباع قواعد LEGB. لكن عند الإسناد، تنشئ بايثون متغيراً محلياً جديداً افتراضياً — x = 10 داخل دالة يُعامل كـ "أنشئ متغيراً محلياً x." إذا كنت حقاً بحاجة لتعديل عالمي، استخدم global. لكن ذلك عادة يعني أن تصميمك يمكن تحسينه.📖 ملخص
- المعاملات الافتراضية تضيف مرونة؛ لا تستخدم أبداً كائنات قابلة للتعديل كمعاملات افتراضية
- الوسائط المفتاحية تتيح التمرير بالاسم، وليس بالموضع
*argsيحزم الوسائط الموضعية الإضافية في صف؛**kwargsيحزم الوسائط المفتاحية في قاموس- ترتيب المعاملات: عادي →
*args→ افتراضية →**kwargs - قاعدة LEGB تحدد ترتيب البحث عن المتغيرات: محلي → محيط → عالمي → مدمج
globalيعدل المتغيرات العالمية من داخل دالة؛ قلّل من استخدامه- العودية هي دالة تستدعي نفسها؛ تحتاج شرط إنهاء وشرط عودي
📝 تمارين
-
أساسي (الصعوبة ⭐): اكتب دالة
multiply(*nums)تقبل أي عدد من الأرقام وتُرجع حاصل ضربها. إذا لم تكن هناك وسائط، أرجع 0. -
متوسط (الصعوبة ⭐⭐): اكتب دالة
create_student(name, **scores)تستقبل اسم طالب وأي عدد من درجات المواد (وسائط مفتاحية)، وتحسب وتُرجع المتوسط. مثال استدعاء:create_student("Alice", Chinese=85, Math=92, English=78). -
تحدي (الصعوبة ⭐⭐⭐): اكتب دالة عودية
sum_digits(n)تحسب مجموع أرقام عدد صحيح موجب. مثال:sum_digits(123)تُرجع1+2+3=6. تلميح: باقي القسمة على 10 يعطي الرقم الأخير؛ القسمة الأرضية على 10 تزيله. شرط الإنهاء: n < 10.



