لیست مطالب

لامبدا (lambda) در پایتون

lambda in python

گاهی در پایتون پیش می‌آید که برای یک محاسبهٔ ساده، ساختن یک تابع کامل با def اضافی به نظر می‌رسد؛ در اینجا lambda کمک می‌کند: تنها در یک خط و بدون نام‌گذاری، ورودی‌ها را می‌گیرد و همان لحظه نتیجهٔ یک عبارت را برمی‌گرداند. این میان‌بُر کد را تمیز و کوتاه نگه می‌دارد و تا زمانی که منطق پیچیده یا چندبارمصرف نباشد، بهترین گزینه است؛ در غیر این صورت، تابع معمولی با def خواناتر و قابل‌نگه‌داری‌تر خواهد بود.

تابع lambda چیست؟

تابع lambda در پایتون یک تابع کوچک و بدون نام است که می‌تواند در همان جایی که تعریف می‌شود استفاده شود. ساختار کلی آن به صورت زیر است:

lambda عبارت :ورودی ها 

وقتی این ساختار را می‌نویسید، پایتون یک شیء تابع می‌سازد که می‌تواند مانند سایر تابع‌ها فراخوانی شود. برای درک بهتر، بیایید مقایسه‌ای انجام دهیم. ابتدا یک تابع عادی برای جمع دو عدد تعریف می‌کنیم و سپس معادل lambda آن را می‌نویسیم:

در مثال پایین، تابع add_numbers با def تعریف شده و دو عدد را جمع می‌کند. در مقابل، sum_two یک تابع lambda است که همان کار را انجام می‌دهد. توجه کنید که در تابع lambda:

  • کلمه‌ی کلیدی def و نام تابع وجود ندارد. 
  • بعد از lambda مستقیماً آرگومان‌ها (x, y) آمده و سپس یک علامت : و در ادامه عبارت جمع x + y. نتیجه‌ی این عبارت به طور خودکار بازگردانده می‌شود (نیازی به نوشتن return نیست). 

در واقع، تابع lambda فوق معادل یک تابع معمولی است که به صورت زیر تعریف شود:

				
					# تابع عادی برای جمع دو عدد
def add_numbers(x, y):
        return x + y

print(add_numbers(3, 5))  #     خروجی: 8

# همان منطق به صورت یک تابع lambda
sum_two = lambda x, y: x + y

print(sum_two(3, 5))      # خروجی: 8


				
			

همان‌طور که مشاهده می‌کنید، پایتون به طور خودکار عبارت بعد از دو نقطه را بازمی‌گرداند. به همین دلیل است که در lambda از return استفاده نمی‌کنیم

شرطی ها در پایتون

هم چنین در پایتون می‌توان منطق شرطی را مستقیماً داخل تابع‌های lambda نوشت؛ به‌شرط آن‌که کل تصمیم‌گیری در قالب یک عبارت بسته شود. عبارت شرطی به شکل زیر نوشته می‌شود و همان لحظه مقدار نهایی را برمی‌گرداند.

				
					result  = value_if_true if condition else value_if_false  # اگر شرط برقرار باشد value_if_true برمی‌گردد؛ در غیر این صورت value_if_false | returns value_if_true if condition is true, else value_if_false
نتیجه = مقدار_صحیح     if شرط      else مقدار_غلط        # returns مقدار_صحیح if شرط is true, else مقدار_غلط | اگر شرط برقرار باشد «مقدار_صحیح» برگردانده می‌شود؛ در غیر این صورت «مقدار_غلط»

				
			

اکنون با استفاده از لامبدا در پایتون می‌خواهیم بدانیم یک عدد زوج است یا فرد.

				
					# لمبدا
is_even = lambda n: "زوج" if n % 2 == 0 else "فرد"

# چند نمونه تک‌به‌تک، هر کدام با خروجی در کامنت
print(is_even(0))   # زوج
print(is_even(1))   # فرد
print(is_even(2))   # زوج
print(is_even(3))   # فرد
print(is_even(4))   # زوج
print(is_even(-5))  # فرد
print(is_even(-6))  # زوج
print(is_even(99))  # فرد

				
			

.

تفاوت‌های تابع lambda با تابع معمولی

تابع‌های lambda از برخی جهات با تابع‌های معمولی (که با def تعریف می‌کنیم) متفاوتند.جدول زیر چند تفاوت کلیدی را نشان می‌دهد:

ویژگی

تابع معمولی (با def)

تابع lambda (لمبدا)

نام تابع

نیاز به نام دارد (مثلاً add_numbers)

بدون نام است (مگر اینکه به متغیری نسبت دهیم)

طول کد

می‌تواند چندین خط باشد و چندین دستور را شامل شود

فقط در یک خط تعریف می‌شود و یک عبارت را شامل می‌شود

کلمه کلیدی تعریف

با def شروع می‌شود و بدنه‌ی تابع در بلاک جداگانه می‌آید

با lambda شروع می‌شود و بدنه فقط یک عبارت بعد از : است

بازگرداندن نتیجه

نیاز به استفاده از return برای برگرداندن نتیجه

نیازی به نوشتن return نیست (خروجی نتیجه‌ی همان عبارت است)

کاربرد

مناسب برای توابع بزرگ‌تر یا استفاده‌های مکرر در برنامه

مناسب برای منطق کوتاه و موقتی (مثلاً وقتی می‌خواهیم یک تابع ساده را همان لحظه به عنوان آرگومان به تابع دیگری بدهیم)

نکته: بدنهٔ تابع‌های lambda نمی‌تواند شامل بیش از یک عبارت یا دستور باشد. به عبارتی دیگر، شما نمی‌توانید درون یک lambda چند خط کد یا عملیات‌های پیچیده (مثل حلقه یا تعریف متغیر جدید) قرار دهید. این تابع‌ها تنها برای انجام یک محاسبه یا عمل ساده طراحی شده‌اند. اگر منطق شما بیش از حد پیچیده یا طولانی است، بهتر است از یک تابع معمولی با def استفاده کنید تا خوانایی برنامه حفظ شود.

حال که با مفهوم و تفاوت‌های lambda آشنا شدیم، در بخش‌های بعدی به کاربردهای آن در موقعیت‌های مختلف می‌پردازیم.

کاربرد lambda در توابع مرتبه بالا در پایتون

یکی از دلایل محبوبیت lambda در پایتون، استفاده آسان از آن به همراه توابعی مانند map، filter و sorted است. این توابع خودشان توابع مرتبه بالا محسوب می‌شوند، یعنی اینکه می‌توانند تابع دیگری را به عنوان ورودی بگیرند و روی داده‌ها اعمال کنند.

اگر بخواهیم توابع مرتبه‌ بالا (Higher-Order Functions) را دقیق تربگوییم :

یک تابع را به عنوان ورودی می‌گیرند (مثل map که تابع دلخواه شما را می‌گیرد و روی همهٔ اعضای لیست اجرا می‌کند).
یا یک تابع را برمی‌گردانند (تابعی می‌سازند و تحویل می‌دهند).
به همین دلیل می‌گوییم توابعی مثل map, filter, sorted «مرتبه‌ بالا» هستند؛ چون ورودی کلیدیِ آن‌ها خودِ یک تابع دیگر است.

در ادامه، هر کدام از این موارد را جداگانه با مثال‌های ساده بررسی می‌کنیم.

استفاده از lambda با تابع map

تابع map یکی از توابع  bulit -in  پایتون است که برای اعمال یک تابع روی تک‌تک اعضای یک لیست (یا هر نوع مجموعه دیگر) به کار می‌رود. به طور دقیق، map یک تابع و یک iterable (مثلاً لیست یا تاپل) را می‌گیرد و آن تابع را روی هر عنصر اعمال می‌کند و نتیجه را به صورت یک آبجکت قابل پیمایش (شبیه لیست) برمی‌گرداند. به زبان ساده، map به ازای هر عضو ورودی یک خروجی جدید می‌سازد.

با استفاده از lambda، می‌توانیم خیلی سریع تابع مورد نظرمان را برای map تعریف کنیم، بدون اینکه لازم باشد آن را جداگانه با def بنویسیم. برای مثال، فرض کنید یک لیست از اعداد داریم و می‌خواهیم مجذور (توان دوم) هر عدد را به دست آوریم:

در ادامه یک مثال کاملاً ساده می‌بینی که یک بار با تابع معمولی (def) و بار دیگر با تابع لمبدا (lambda) مجذور هر عدد را می‌سازد. هر دو روش دقیقاً یک نتیجه می‌دهند؛ فقط شکل نوشتنشان فرق می‌کند.

۱. با تابع معمولی (def)

				
					
def square(x):
        return x ** 2   # توانِ دوم عدد را برمی‌گرداند

# ❷ فهرست اعداد
numbers = [1, 2, 3, 4, 5]

# ❸ با map تابع square را روی تک‌تک اعداد اجرا می‌کنیم
squares = list(map(square, numbers))

print(squares)  # خروجی: [1, 4, 9, 16, 25]


				
			

چه اتفاقی افتاد؟

  • تابع square یک نام مستقل دارد و هر جا بخواهیم می‌توانیم دوباره از آن استفاده کنیم.
  • map این تابع را روی عناصر لیست اجرا کرد و نتیجه را داد.

۲. با تابع لمبدا (lambda)

				
					# فهرست همان است
numbers = [1, 2, 3, 4, 5]

# این بار به جای تابع معمولی، lambda می‌دهیم
squares = list(map(lambda x: x ** 2, numbers))

print(squares)  # خروجی: [1, 4, 9, 16, 25]

				
			

در این کد:

  • لیستی به نام numbers تعریف کرده‌ایم.
  • از map استفاده کرده‌ایم و به عنوان ورودی اول یک تابع lambda داده‌ایم که هر عدد x را به توان ۲ می‌رساند (x ** 2).
  • خروجی map یک آبجکت قابل پیمایش است که برای مشاهده نتایج آن را با list() به لیست تبدیل کرده و چاپ کرده‌ایم. نتیجه لیستی از مجذور اعداد ورودی است.

تابع lambda در اینجا به صورت درجا تعریف شده و نیاز به نام‌گذاری نداشتیم. معادل همین کار بدون lambda این بود که یک تابع مثلاً به نام square تعریف کنیم و سپس آن را به map بدهیم. اما با lambda کد ما کوتاه‌تر و خواناتر شده است. به کار بردن lambda داخل map باعث می‌شود نیازی به تعریف تابع جداگانه و نام‌گذاری آن نداشته باشیم و اصطلاحاً کد تمیزتر و مختصر‌تری داشته باشیم.

مثال :تشخیص «زوج/فرد» در لیست nums

در روش اول یک تابع معمولی با def می‌سازیم؛ در روش دوم همان منطق را با یک تابع یک‌خطی و بی‌نام (lambda) پیاده می‌کنیم. خروجی هر دو یکسان است، اما هر کدام مزیت‌های خاص خود را دارند.

				
					nums = [1, 2, 3, 4]

# ➊ تابع معمولی
def even_or_odd(n):
        return 'زوج' if n % 2 == 0 else 'فرد'

labels_def = list(map(even_or_odd, nums))
print(labels_def)          # ['فرد', 'زوج', 'فرد', 'زوج']


# ➋ لمبدا (lambda)
labels_lambda = list(
    map(lambda n: 'زوج' if n % 2 == 0 else 'فرد', nums)
)
print(labels_lambda)       # ['فرد', 'زوج', 'فرد', 'زوج']

				
			

مثال: استفاده از map برای پردازش رشته‌ها

تابع map محدود به اعداد نیست. هر عملی که بتوان به صورت تابعی روی اعضای یک مجموعه انجام داد را می‌توان با map و lambda پیاده کرد. به عنوان نمونه، یک لیست از رشته‌ها را در نظر بگیرید که می‌خواهیم طول هر رشته را به دست آوریم:

				
					
fruits = ['سیب', 'موز', 'گیلاس']  # توجه: این رشته‌ها به فارسی هستند
lengths = list(map(lambda x: len(x), fruits))
print(lengths)  # خروجی: [3, 3, 4]

				
			

در اینجا با استفاده از len(x) داخل lambda، طول هر رشته محاسبه شده است. نتیجه نهایی نشان می‌دهد که طول کلمه‌های ‘سیب’ و ‘موز’ برابر ۳ و طول ‘گیلاس’ ۴ است. این مثال نشان می‌دهد که lambda حتی می‌تواند توابع توکار دیگر (مثل len) را صدا بزند یا هر عمل ساده‌ای را انجام دهد.

استفاده از lambda با تابع filter

تابع built-in (درونی)  بعدی که بسیار در ترکیب با lambda کاربرد دارد، filter است. همان‌طور که از نامش پیداست، filter برای فیلتر کردن عناصر یک مجموعه بر اساس یک شرط به کار می‌رود. این تابع یک تابع شرطی و یک iterable می‌گیرد و خروجی آن شامل عناصری از iterable است که تابع شرطی برای آن‌ها True (صحیح) برگرداند. به عبارت دیگر، filter تمام اعضای لیست (یا مجموعه ورودی) را یکی‌یکی بررسی می‌کند و فقط آن‌هایی را نگه می‌دارد که شرط مورد نظر را تأمین می‌کنند.

به کمک lambda می‌توانیم به سرعت یک شرط دلخواه را تعریف کنیم. فرض کنید می‌خواهیم از یک لیست اعداد، فقط اعداد زوج را جدا کنیم:

				
					# تابع زوج بودن
def is_even(x):
        return x % 2 == 0    # اگر باقی‌مانده تقسیم بر ۲ صفر باشد → زوج است

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens   = list(filter(is_even, numbers))

print(evens)   # خروجی: [2, 4, 6, 8]

				
			

آنچه رخ داد:

تابع is_even را تعریف کردیم؛ هر عدد را گرفت، شرط زوج بودن را بررسی کرد.

filter این تابع را روی همهٔ عناصر اجرا کرد و فقط موارد True را نگه داشت.

در پایان، نتیجه را به لیست تبدیل و چاپ کردیم.

۲) روش تابع لمبدا (lambda)

				
					

numbers = [1, 2, 3, 4, 5, 6, 7, 8]

evens = list(filter(lambda x: x % 2 == 0, numbers))

print(evens)   # خروجی: [2, 4, 6, 8]

				
			

تفاوت اصلی:

تابع را جداگانه تعریف نکردیم؛ شرط x % 2 == 0 را همان لحظه با lambda نوشتیم.

این روش وقتی خوب است که منطق ساده باشد و فقط یک‌بار به آن نیاز داریم؛ کد کوتاه‌تر و جمع‌وجور می‌شود.

				
					
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))  # خروجی: [2, 4, 6, 8]

				
			
در این مثال:
  • تابع lambda ما هر عدد x را می‌گیرد و شرط x % 2 == 0 (باقی‌مانده تقسیم بر ۲ برابر صفر باشد) را بررسی می‌کند. این شرط برای اعداد زوج True خواهد بود.
  • filter این lambda را روی تک‌تک اعضای لیست numbers اعمال می‌کند و هر جا نتیجه True باشد آن عدد را در خروجی نگه می‌دارد.
  • خروجی نهایی که به لیست تبدیل شده، شامل تمام اعداد زوج لیست اولیه است: [2, 4, 6, 8].
مانند map، خروجی filter هم یک شیء قابل پیمایش (فیلترآبجکت) است که برای مشاهده محتوا معمولاً آن را به لیست تبدیل می‌کنیم. به جای lambda، امکان داشت یک تابع مثلاً به نام is_even تعریف کنیم و به filter بدهیم، اما استفاده از lambda کد را بسیار خلاصه کرده و تنها چیزی که لازم داریم – یعنی شرط انتخاب اعداد زوج – دقیقاً همان‌جا که لازم است تعریف شده است. این امر خوانایی کد را در مثال‌های ساده افزایش می‌دهد (چون می‌توان سریع متوجه شد در حال فیلتر چه شرطی اعمال می‌شود).

استفاده از lambda با تابع sorted در پایتون

سومین سناریوی رایج برای کاربرد lambda، مرتب‌سازی مجموعه‌ها با معیار خاص است. در پایتون، تابع  ،sorted یک لیست (یا هر مجموعه قابل مرتب‌سازی) را گرفته و یک لیست مرتب‌شده جدید برمی‌گرداند. به طور پیش‌فرض، sorted عناصر را به صورت صعودی (کوچک به بزرگ یا الفبایی) مرتب می‌کند. اما فرض کنید می‌خواهیم بر اساس معیار متفاوتی مرتب‌سازی کنیم – مثلاً بر اساس طول رشته، قدرمطلق اعداد، یا یک جزء خاص از اشیاء داخل لیست. اینجاست که پارامتر key در تابع sorted به کمک ما می‌آید.

پارامتر key به ما اجازه می‌دهد یک تابع به sorted بدهیم تا مقادیر بر اساس خروجی آن تابع مقایسه و مرتب شوند. این تابع دقیقاً همان جایی است که lambda می‌تواند کار را ساده کند. در ادامه چند مثال از مرتب‌سازی سفارشی با lambda را می‌بینیم:

مثال ۱: مرتب‌سازی بر اساس طول رشته‌ها
فرض کنید یک لیست شامل نام چند رنگ به صورت رشته‌ای داریم و می‌خواهیم آن‌ها را بر اساس طول حروف مرتب کنیم، نه ترتیب حروف الفبا. با استفاده از lambda به شکل زیر می‌توانیم این کار را انجام دهیم:

				
					
colors = ['blue', 'red', 'yellow', 'green']
sorted_by_length = sorted(colors, key=lambda x: len(x))
print(sorted_by_length)  # خروجی: ['red', 'blue', 'green', 'yellow']

				
			

در اینجا sorted لیست colors را گرفته است. به عنوان کلید مرتب‌سازی (key) یک تابع lambda تعریف کرده‌ایم که طول (len(x)) هر رشته را برمی‌گرداند. بنابراین sorted بر اساس این طول‌ها مرتب‌سازی را انجام می‌دهد. نتیجه، همان‌طور که می‌بینید، لیستی است که از کوتاه‌ترین کلمه (‘red’ با ۳ حرف) تا بلندترین (‘yellow’ با ۶ حرف) مرتب شده است. به صورت پیش‌فرض، مرتب‌سازی صعودی انجام شده (از کم به زیاد). اگر می‌خواستیم معکوس (نزولی) باشد، می‌توانستیم sorted(colors, key=lambda x: len(x), reverse=True) را استفاده کنیم.

				
					
numbers = [3, -1, 10, -5, 2]
sorted_by_abs = sorted(numbers, key=lambda x: abs(x))
print(sorted_by_abs)  # خروجی: [2, -1, 3, -5, 10]

				
			

در این مثال، کلید مرتب‌سازی را abs(x) قرار داده‌ایم که قدرمطلق هر عدد را محاسبه می‌کند. حال sorted این لیست را نه بر اساس خود اعداد بلکه بر پایهٔ قدرمطلق آن‌ها مرتب کرده است. نتیجه: [2, -1, 3, -5, 10]. اگر دقت کنید، این ترتیب بر اساس مقدار عددی بدون در نظر گرفتن علامت است (۲ و -1 که قدرمطلق ۱ دارند جلوتر آمده‌اند، 3 و -5 (قدرمطلق 5) در ادامه و 10 که بزرگ‌ترین قدرمطلق را دارد آخر قرار گرفته است).

مثال ۳: مرتب‌سازی بر اساس جزء مشخصی از هر عنصر
گاهی عناصر لیست، چند بخشی هستند؛ مثل لیستی از تاپل‌ها یا دیکشنری‌ها. در چنین مواردی نیز lambda می‌تواند برای تعیین معیار مرتب‌سازی استفاده شود. برای نمونه، فرض کنید لیستی از تاپل‌ها داریم که هر تاپل دو عدد را نگهداری می‌کند. می‌خواهیم این تاپل‌ها را بر اساس دومین مقدار هر تاپل مرتب کنیم:

				
					
data = [(1, 3), (2, 1), (4, 2)]
sorted_by_second = sorted(data, key=lambda x: x[1])
print(sorted_by_second)  # خروجی: [(2, 1), (4, 2), (1, 3)]


				
			

در این قطعه‌کد، هر عنصر x یک تاپل دو عددی مثل (1, 3) است. تابع lambda با x[1] مقدار دوم تاپل را برمی‌گرداند و sorted بر اساس این مقدار دوم مرتب‌سازی را انجام می‌دهد. نتیجه نشان می‌دهد که لیست ابتدا بر اساس کوچک‌ترین مقدار دوم (1) تا بزرگ‌ترین (3) مرتب شده است. این مثال کاربرد lambda را در مرتب‌سازی ساختارهای داده پیچیده‌تر نشان می‌دهد.

به طور خلاصه، هر زمانی که بخواهید معیاری ویژه برای مرتب‌سازی تعریف کنید، کافیست آن معیار را به شکل یک تابع lambda به sorted (یا متد مشابه sort روی خود لیست‌ها) بدهید. تابع sorted عناصر را طبق کلیدی که شما مشخص می‌کنید مرتب کرده و یک لیست جدید به شما می‌دهد.

نکات مهم درباره استفاده از lambda در پایتون

تابع‌های lambda ابزار بسیار قدرتمندی برای کوتاه و خوانا کردن کد هستند، به ویژه وقتی در جای درست به کار بروند. در اینجا چند نکته نهایی راجع به استفاده از آن‌ها مطرح می‌کنیم:

  • سادگی منطق: lambda تنها برای مواقعی مناسب است که منطق تابع شما ساده و تک‌عبارتی باشد. اگر منطق پیچیده‌ای دارید یا نیاز به چندین مرحله محاسبه دارید، از def و تعریف تابع معمولی استفاده کنید تا کدتان قابل فهم‌تر باشد. همان‌طور که گفتیم، lambda‌ها محدود به یک عبارت هستند و نمی‌توان چند دستور مجزا در آن‌ها قرار داد.
  • خوانایی کد: اگرچه lambda کد را کوتاه می‌کند، اما زیاده‌روی در استفاده از آن می‌تواند خوانایی را کاهش دهد. سعی کنید از lambda زمانی استفاده کنید که واقعاً کد را واضح‌تر می‌کند. در غیر این صورت، یک تابع معمولی با توضیحات کافی ممکن است برای دیگران (و حتی خودتان در آینده) قابل درک‌تر باشد.
  • استفاده مجدد: تابع‌های lambda معمولاً برای استفاده‌های موقت طراحی شده‌اند. اگر قرار است یک منطق را در چند جای مختلف برنامه به کار ببرید، بهتر است یک بار آن را با def تعریف کرده و نام‌گذاری کنید. این کار هم از تکرار کد جلوگیری می‌کند و هم امکان استفادهٔ مجدد (reuse) را فراهم می‌کند.
  • تابع‌های buiit-in پایتون: گاهی اوقات پایتون خودش توابع کوچکی دارد که می‌توانند به عنوان کلید یا عملگر استفاده شوند (مثلاً len, abs, str.lower و غیره). در چنین مواقعی می‌توانید مستقیماً از آن‌ها به عنوان key یا در map استفاده کنید و دیگر نیازی به lambda نیست. برای مثال، به جای sorted(list, key=lambda x: len(x)) می‌توان مستقیماً نوشت sorted(list, key=len). بنابراین همیشه از خود بپرسید آیا تابع سادهٔ شما قبلاً وجود دارد یا نه.

منابع:

python.org

نوشته های مرتبط

نظرات کاربران

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *