المعاملات المتقدمة والنطاق

الآن بعد أن تعلمت أساسيات الدوال، دعنا نستكشف بعض التقنيات المتقدمة. كم طريقة يمكنك كتابة المعاملات بها؟ ماذا يعني "نطاق المتغير"؟ ما هي العودية؟ هذه المفاهيم سترفع فهمك للدوال إلى المستوى التالي.


1. المعاملات الافتراضية

المعاملات الافتراضية تتيح لك حذف الوسائط عند استدعاء دالة:

مثال: المعاملات الافتراضية الأساسية

PYTHON
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")              # Hello, Alice!
greet("Bob", "Hi")          # Hi, Bob!
greet("Charlie", "Hey")     # Hey, Charlie!
▶ جرّب الكود

⚠️ فخ المعامل الافتراضي

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

مثال: فخ المعامل الافتراضي القابل للتعديل

PYTHON
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 كافتراضي وأنشئ قائمة جديدة داخل الدالة:

مثال: معامل افتراضي آمن

PYTHON
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. الوسائط المفتاحية

عند استدعاء دالة، يمكنك تحديد القيم باسم المعامل، مما يسمح بتمرير الوسائط خارج الترتيب:

مثال: الوسائط المفتاحية

PYTHON
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 لدالة الجمع

PYTHON
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

PYTHON
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 لبناء ملفات تعريف المستخدمين

PYTHON
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")
▶ جرّب الكود

المخرجات:

TEXT
User Profile:
  name: Alice
  age: 25
  city: Beijing
  job: Engineer

مثال: دمج *args و **kwargs

PYTHON
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

PYTHON
# نطاق عالمي
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 تعني:

تبحث بايثون عن المتغيرات بترتيب L → E → G → B.

global و nonlocal

لتعديل متغير عالمي داخل دالة، استخدم global:

مثال: استخدام global

PYTHON
count = 0

def increment():
    global count     # أعلن عن نية تعديل المتغير العالمي
    count += 1

increment()
increment()
print(count)         # 2
▶ جرّب الكود
⚠️ قلّل من استخدام global. المتغيرات العالمية تجعل الكود أصعب في الفهم والتصحيح. يجب أن تستقبل الدوال المدخلات من خلال المعاملات وتُخرج النتائج من خلال return، وليس تعديل المتغيرات العالمية سراً.


6. أساسيات العودية

العودية هي عندما تستدعي دالة نفسها. الفكرة الأساسية: قسم مشكلة كبيرة إلى مشكلات فرعية أصغر ومتشابهة.

مثال: العودية لحساب المضروب

PYTHON
def factorial(n):
    """احسب n! (مضروب n)"""
    if n <= 1:
        return 1          # شرط الإنهاء
    return n * factorial(n - 1)   # استدعاء عودي

print(factorial(5))        # 120 (5×4×3×2×1)
▶ جرّب الكود

التنفيذ:

TEXT
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

مثال: متتالية فيبوناتشي (الصعوبة ⭐⭐⭐)

PYTHON
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؟
ج نعم. *args و kwargs مجرد اصطلاحات تسمية — العلامات النجمية هي المهمة. يمكنك كتابة *numbers، **options. لكن اتباع الاصطلاح موصى به حتى يفهمه مبرمجو بايثون الآخرون فوراً. ⚠️ س:العودية مقابل الحلقات — أيهما أفضل؟ ج:إذا كانت الحلقة يمكنها حل المشكلة، فضّل الحلقة. الحلقات أداء أفضل، ولن تسبب فيضاً في المكدس، وأسهل في الفهم. العودية مناسبة لسيناريوهين: اجتياز الهياكل الشجرية (الدلائل، JSON) والتعريفات الرياضية العودية (المضروب، فيبوناتشي). العودية أكثر إيجازاً لكنها أصعب في الفهم. س:لماذا يمكنني قراءة متغير عالمي داخل دالة لكن لا يمكنني تعديله؟ ج:عند القراءة، تجد بايثون المتغير العالمي باتباع قواعد LEGB. لكن عند الإسناد، تنشئ بايثون متغيراً محلياً جديداً افتراضياً — x = 10 داخل دالة يُعامل كـ "أنشئ متغيراً محلياً x." إذا كنت حقاً بحاجة لتعديل عالمي، استخدم global. لكن ذلك عادة يعني أن تصميمك يمكن تحسينه.

📖 ملخص

  • المعاملات الافتراضية تضيف مرونة؛ لا تستخدم أبداً كائنات قابلة للتعديل كمعاملات افتراضية
  • الوسائط المفتاحية تتيح التمرير بالاسم، وليس بالموضع
  • *args يحزم الوسائط الموضعية الإضافية في صف؛ **kwargs يحزم الوسائط المفتاحية في قاموس
  • ترتيب المعاملات: عادي → *args → افتراضية → **kwargs
  • قاعدة LEGB تحدد ترتيب البحث عن المتغيرات: محلي → محيط → عالمي → مدمج
  • global يعدل المتغيرات العالمية من داخل دالة؛ قلّل من استخدامه
  • العودية هي دالة تستدعي نفسها؛ تحتاج شرط إنهاء وشرط عودي

📝 تمارين

  1. أساسي (الصعوبة ⭐): اكتب دالة multiply(*nums) تقبل أي عدد من الأرقام وتُرجع حاصل ضربها. إذا لم تكن هناك وسائط، أرجع 0.

  2. متوسط (الصعوبة ⭐⭐): اكتب دالة create_student(name, **scores) تستقبل اسم طالب وأي عدد من درجات المواد (وسائط مفتاحية)، وتحسب وتُرجع المتوسط. مثال استدعاء: create_student("Alice", Chinese=85, Math=92, English=78).

  3. تحدي (الصعوبة ⭐⭐⭐): اكتب دالة عودية sum_digits(n) تحسب مجموع أرقام عدد صحيح موجب. مثال: sum_digits(123) تُرجع 1+2+3=6. تلميح: باقي القسمة على 10 يعطي الرقم الأخير؛ القسمة الأرضية على 10 تزيله. شرط الإنهاء: n < 10.

100%