Kodax

الدوال كقيم: Lambda والدوال العليا والإغلاقات — Python المتقدم

الدوال كقيم: Lambda والدوال العليا والإغلاقات

في Python الدالة كائن مثل أي قيمة: يمكن وضعها في متغير، وتمريرها لدالة أخرى، وإرجاعها من دالة. هذه الفكرة أساس كل ما في هذه الدورة تقريباً (decorators، مفاتيح الترتيب، callbacks).

def shout(text):
    return text.upper() + "!"

speak = shout          # بدون أقواس: لا نستدعي الدالة، بل نشير إليها
print(speak("مرحبا"))

الدوال العليا (Higher-Order Functions)

دالة تأخذ دالة كمعامل أو ترجع دالة:

def apply_twice(func, value):
    return func(func(value))

print(apply_twice(lambda x: x * 3, 2))   # 18

lambda

دالة صغيرة بلا اسم من تعبير واحد: lambda المعاملات: التعبير. مناسبة للاستخدام لمرة واحدة كمعامل (key=lambda p: p.price). إذا احتجت أكثر من تعبير أو اسماً واضحاً، اكتب def.

map و filter و reduce

nums = [1, 2, 3, 4, 5]
list(map(lambda n: n * 2, nums))        # [2, 4, 6, 8, 10]
list(filter(lambda n: n % 2, nums))     # [1, 3, 5]
from functools import reduce
reduce(lambda acc, n: acc + n, nums)    # 15

في Python الحديثة غالباً تكون comprehensions أوضح من map و filter: [n * 2 for n in nums]. أما reduce فاستخدم بدلها دوالاً جاهزة مثل sum و max و math.prod كلما أمكن.

الإغلاق (Closure)

دالة داخلية تتذكر المتغيرات من الدالة التي أنشأتها حتى بعد انتهائها:

def make_multiplier(factor):
    def multiply(x):
        return x * factor      # factor من النطاق الخارجي
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5))    # 10 15

كل استدعاء لـ make_multiplier ينشئ دالة جديدة تحمل قيمة factor الخاصة بها. هذا بالضبط ما تقوم عليه الـ decorators في الدرس التالي.

nonlocal

لتعديل (وليس فقط قراءة) متغير من النطاق الخارجي داخل الإغلاق:

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c = counter()
print(c(), c(), c())   # 1 2 3

functools.partial

تثبيت بعض معاملات دالة لإنشاء دالة أبسط: partial(pow, exp=2).

فخ شائع: الإغلاق داخل حلقة

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2] وليس [0, 1, 2]
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])        # [0, 1, 2]

الإغلاق يتذكر المتغير لا قيمته لحظة الإنشاء؛ القيمة الافتراضية i=i تثبّت القيمة.

تمرين

اكتب make_validator(min_len) ترجع دالة تتحقق من طول كلمة مرور، و make_counter() ترجع دالتين: زيادة وإعادة تعيين، تتشاركان نفس العدّاد.

# Lambda بسيطة
square = lambda x: x ** 2
print(square(5))  # 25

add = lambda x, y: x + y
print(add(3, 4))  # 7

# map - تطبيق دالة على كل عنصر
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared)  # [1, 4, 9, 16, 25]

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

doubled = list(map(double, numbers))
print(doubled)  # [2, 4, 6, 8, 10]

# filter - تصفية العناصر
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4]

# تصفية الأسماء الطويلة
names = ["أحمد", "محمد علي", "سارة", "عبدالرحمن"]
long_names = list(filter(lambda n: len(n) > 4, names))
print(long_names)

# reduce - تجميع
from functools import reduce

total = reduce(lambda x, y: x + y, numbers)
print(total)  # 15

# إيجاد الأكبر
maximum = reduce(lambda x, y: x if x > y else y, numbers)
print(maximum)  # 5

# sorted مع key
students = [
    {"name": "أحمد", "score": 85},
    {"name": "محمد", "score": 92},
    {"name": "علي", "score": 78}
]

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

# ترتيب نصوص حسب الطول
words = ["programming", "is", "fun", "and", "challenging"]
by_length = sorted(words, key=len)
print(by_length)

# دمج map و filter
result = list(map(
    lambda x: x ** 2,
    filter(lambda x: x % 2 == 0, range(10))
))
print(result)  # [0, 4, 16, 36, 64]

# دالة ترجع دالة
def multiplier(n):
    return lambda x: x * n

double = multiplier(2)
triple = multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15
اختبر فهمك