دوال الرتبة العالية و Lambda

في بايثون، الدوال هي أيضاً "قيم" — يمكن إسنادها إلى متغيرات، تمريرها إلى دوال أخرى، تماماً مثل الأرقام والسلاسل النصية. هذا يتيح كوداً موجزاً وأنيقاً جداً. دوال الرتبة العالية هي دوال تأخذ دوالاً أخرى كوسائط أو تُرجع دوالاً. هذا الدرس سيفتح لك عالماً جديداً.


1. الدوال كمواطنين من الدرجة الأولى

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

مثال: الدوال كقيم

PYTHON
def say_hello(name):
    return f"Hello, {name}"

# أسند دالة إلى متغير
my_func = say_hello
print(my_func("Alice"))      # Hello, Alice

# مرر دالة كوسيطة
def greet_with(func, name):
    return func(name)

print(greet_with(say_hello, "Bob"))   # Hello, Bob

# أرجع دالة من دالة
def create_greeter(greeting):
    def greeter(name):
        return f"{greeting}, {name}!"
    return greeter

say_hi = create_greeter("Hi")
say_hello = create_greeter("Hello")

print(say_hi("Alice"))       # Hi, Alice!
print(say_hello("Bob"))      # Hello, Bob!
▶ جرّب الكود
💡 ماذا يعني "الدوال كمواطنين من الدرجة الأولى"؟ يعني أن الدوال في نفس مستوى الأعداد الصحيحة والسلاسل النصية والقوائم — يمكنك إسنادها إلى متغيرات، وضعها في قوائم، تمريرها كوسائط، وإرجاعها كقيم. هذا النموذج البرمجي يُسمى "البرمجة الوظيفية."


2. map(): التحويل الجماعي

map() تطبق دالة على كل عنصر في تسلسل وتُرجع مكرراً جديداً:

مثال: map تحويل جماعي

PYTHON
# ربّع كل رقم في قائمة
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared)              # [1, 4, 9, 16, 25]

# حوّل قائمة سلاسل نصية إلى أحرف كبيرة
fruits = ["apple", "banana", "cherry"]
upper_fruits = list(map(str.upper, fruits))
print(upper_fruits)         # ['APPLE', 'BANANA', 'CHERRY']

# مئوي إلى فهرنهايت
temps_c = [0, 10, 20, 30, 40]
temps_f = list(map(lambda c: c * 9 / 5 + 32, temps_c))
print(temps_f)              # [32.0, 50.0, 68.0, 86.0, 104.0]
▶ جرّب الكود

map() تعادل شاملة قائمة: map(f, seq)[f(x) for x in seq]. أي استخدام هو مسألة تفضيل — الشاملات أكثر "بايثونية."


3. filter(): التصفية الجماعية

filter() تختار العناصر التي تُرجع الدالة True لها:

مثال: filter بيانات

PYTHON
# صفّ الأرقام الزوجية
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)                # [2, 4, 6, 8, 10]

# صفّ الكلمات بطول >= 5
words = ["cat", "elephant", "dog", "giraffe", "ant"]
long_words = list(filter(lambda w: len(w) >= 5, words))
print(long_words)           # ['elephant', 'giraffe']

# صفّ السلاسل غير الفارغة
data = ["hello", "", "world", "", "python"]
non_empty = list(filter(None, data))   # None تعني "أزل القيم falsy"
print(non_empty)            # ['hello', 'world', 'python']
▶ جرّب الكود

filter() أيضاً تعادل شاملة: filter(f, seq)[x for x in seq if f(x)]. استخدام None كدالة يعادل إزالة جميع القيم falsy ("", 0, None, False، إلخ.).


4. استخدام sorted() المتقدم

الدرس 11 غطى معامل key في sorted(). دعنا نتعمق أكثر:

مثال: ترتيب مخصص باستخدام sorted

PYTHON
# ترتيب حسب سمة الكائن
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 92},
    {"name": "Charlie", "score": 78},
]

# ترتيب حسب الدرجة تنازلياً
ranked = sorted(students, key=lambda s: s["score"], reverse=True)
for s in ranked:
    print(f"{s['name']}: {s['score']}")

# ترتيب حسب طول الاسم
sorted_by_name = sorted(students, key=lambda s: len(s["name"]))
print([s["name"] for s in sorted_by_name])   # ['Alice', 'Bob', 'Charlie']
▶ جرّب الكود

مثال: ترتيب متعدد المستويات (الصعوبة ⭐⭐)

PYTHON
# ترتيب حسب الدرجة تنازلياً، ثم حسب الاسم أبجدياً
data = [
    ("Alice", 85),
    ("Bob", 92),
    ("Charlie", 85),
    ("Diana", 92),
]

def sort_key(item):
    return (-item[1], item[0])

sorted_data = sorted(data, key=sort_key)
for name, score in sorted_data:
    print(f"{name}: {score}")
▶ جرّب الكود

المخرجات:

TEXT
Bob: 92
Diana: 92
Alice: 85
Charlie: 85

5. reduce(): الحساب التراكمي

reduce() يُجمّع العناصر في تسلسل، مطبقاً دالة على عنصرين في كل مرة. إنها ليست مدمجة؛ استوردها من functools:

مثال: reduce حساب تراكمي

PYTHON
from functools import reduce

# حاصل ضرب جميع الأرقام
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product)              # 120 (1×2×3×4×5)

# ابحث عن القيمة القصوى
max_val = reduce(lambda a, b: a if a > b else b, [3, 1, 4, 1, 5])
print(max_val)              # 5

# دمج السلاسل النصية
words = ["Hello", " ", "World", " ", "Python"]
sentence = reduce(lambda a, b: a + b, words)
print(sentence)             # Hello World Python
▶ جرّب الكود

عملية تنفيذ reduce()

PYTHON
# reduce(lambda x, y: x + y, [1, 2, 3, 4])
# الخطوة 1: x=1, y=2 → 3
# الخطوة 2: x=3, y=3 → 6
# الخطوة 3: x=6, y=4 → 10
# النتيجة: 10
💡 reduce() مقابل الحلقات: أي شيء يمكن لـ reduce() فعله، يمكن لحلقة for فعله أيضاً. reduce() أكثر إيجازاً وتصريحاً — أنت تقول "ماذا تفعل" بدلاً من "كيف تفعله." الجانب السلبي هو أنه قد يكون أصعب في الفهم للوافدين الجدد.


6. تعبيرات Lambda المتعمقة

Lambda، المسماة على اسم الحرف اليوناني λ، هي أساساً دالة مجهولة — دالة صغيرة بدون اسم، تُستخدم ثم تُتجاهل.

Lambda مقابل الدوال العادية

مثال: Lambda مقابل عادية

PYTHON
# دالة عادية
def double(x):
    return x * 2

# تعبير lambda
double = lambda x: x * 2

print(double(5))            # 10
▶ جرّب الكود

صيغة Lambda الكاملة

PYTHON
lambda parameters: expression
# ↑ تعادل
def anonymous_function(parameters):
    return expression

معاملات متعددة

مثال: Lambda مع معاملات متعددة

PYTHON
add = lambda a, b: a + b
print(add(3, 5))            # 8

# تعبير ثلاثي في lambda
max_val = lambda a, b: a if a > b else b
print(max_val(10, 20))      # 20
▶ جرّب الكود

متى تستخدم Lambda

مثال: حالات استخدام Lambda

PYTHON
# ✅ جيد: تحويل بسيط من سطر واحد كوسيطة لدالة رتبة عالية
numbers = [1, 2, 3, 4]
result = list(map(lambda x: x ** 2, numbers))

# ❌ سيء: منطق معقد يتطلب أسطراً متعددة — استخدم دالة def عادية بدلاً من ذلك
▶ جرّب الكود
💡 قاعدة Lambda الأساسية: إذا كان المنطق يحتاج أكثر من تعبير واحد أو تفرع if-elif، لا تستخدم lambda — اكتب دالة عادية بدلاً من ذلك. أناقة lambda تكمن في "بساطتها."


حالات الاستخدام الشائعة


❓ أسئلة شائعة

س أيهما أفضل — map() أم شاملات القوائم؟
ج مجتمع بايثون يفضل شاملات القوائم — أكثر بديهية، أكثر بايثونية. map() لها مزايا: ① أقصر عندما يكون لديك اسم دالة موجودة (map(str.upper, seq) مقابل [s.upper() for s in seq])؛ ② تُرجع مكرراً كسولاً، يوفر الذاكرة للبيانات الكبيرة. في معظم الحالات، شاملات القوائم هي الخيار الأفضل. ⚠️ س:هل يمكن لـ lambda أن تمتد عبر أسطر متعددة؟ ج:لا. صيغة lambda مقيدة بتعبيرات مفردة. للمنطق متعدد الأسطر، عرّف دالة عادية. Lambda في بايثون أضعف عمداً منها في لغات أخرى (مثل JavaScript) — غيدو (منشئ بايثون) يعتقد أن "الصريح أفضل من الضمني." س:هل reduce() لا تزال مستخدمة في بايثون الحديثة؟ ج:ليس كثيراً. غيدو نفسه لم يكن معجباً بـ reduce()، معتبراً إياها صعبة الفهم. معظم سيناريوهات reduce() يمكن استبدالها بحلقة أو دالة أكثر مباشرة (مثل sum()، max()). ومع ذلك، تبقى reduce() مفهوماً مهماً في البرمجة الوظيفية.

📖 ملخص

  • الدوال هي مواطنون من الدرجة الأولى في بايثون — يمكن إسنادها وتمريرها وإرجاعها
  • map(f, seq) تطبق دالة على كل عنصر؛ استخدم list() للتحويل إلى قائمة
  • filter(f, seq) تختار العناصر حيث تُرجع الدالة True
  • sorted(seq, key=f) يقبل دالة للترتيب المخصص
  • reduce(f, seq) يُجمّع القيم؛ استورد من functools
  • Lambda lambda params: expression هي دالة مجهولة من سطر واحد للسيناريوهات البسيطة

📝 تمارين

  1. أساسي (الصعوبة ⭐): بالنظر إلى nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]، استخدم map() و filter() لـ: تصفية الأرقام الفردية وحساب مربعاتها. تلميح: أولاً filter للفردية، ثم map للتربيع.

  2. متوسط (الصعوبة ⭐⭐): بالنظر إلى قائمة من القواميس products = [{"name": "A", "price": 100}, {"name": "B", "price": 80}, {"name": "C", "price": 120}]، استخدم sorted() للترتيب حسب السعر تنازلياً وأخرج الأسماء والأسعار.

  3. تحدي (الصعوبة ⭐⭐⭐): استخدم reduce() لتنفيذ دالة flatten(nested_list) التي تسطّح قائمة متداخلة بمستوى واحد. مثال: flatten([[1, 2], [3, 4], [5]]) تُرجع [1, 2, 3, 4, 5]. تلميح: في reduce، استخدم + لدمج قائمتين في كل مرة. ثم نفذ نفس الشيء بشاملة قائمة وقارن بين النهجين.

100%