۴ کاربرد نادرست لامبدا در پایتون — راهنمای کاربردی

۱۳۱ بازدید
آخرین به‌روزرسانی: ۲۸ خرداد ۱۴۰۱
زمان مطالعه: ۶ دقیقه
۴ کاربرد نادرست لامبدا در پایتون — راهنمای کاربردی

لامبداها به عنوان یکی از ویژگی‌های خاص زبان برنامه‌نویسی پایتون، محبوبیت زیادی کسب کرده‌اند، به طوری که بسیاری از برنامه‌نویسان می‌خواهند در هر موقعیتی از آن‌ها استفاده کنند. بدیهی است که لامبداها موجب انسجام بیشتر کد می‌شوند، اما استفاده نابجا از آن‌ها ممکن است منجر به کاهش خوانایی و قابلیت نگهداری کد شود. در این مقاله با 4 کاربرد نادرست لامبدا در پایتون آشنا خواهیم شد.

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

لامبدا که به عنوان تابع لامبدا (Lambda) نیز شناخته می‌شود، تابع بی‌اسمی است که هر تعداد آرگومان می‌گیرد و تنها یک عبارت دارد. اعلان لامبدا به وسیله کلیدواژه lambda مشخص می‌شود. ساختار ابتدایی آن چنین است:

lambda arguments: expression

لامبداها بهترین گزینه برای تعریف تابع‌های کوچکی هستند که تنها یکبار استفاده می‌شوند. یکی از کاربردهای رایج لامبدا برای تعیین آرگومان کلید در تابع داخلی ()sorted است. به مثال زیر توجه کنید:

>>> students = [('Mike', 'M', 15), ('Mary', 'F', 14), ('David', 'M', 16)]
>>> sorted(students, key=lambda x: x[2])
[('Mary', 'F', 14), ('Mike', 'M', 15), ('David', 'M', 16)]
# The students are sorted by age

راهنماهای زیادی در مورد توضیح ماهیت لامبداها و کاربردهایشان وجود دارند و از این رو در این مقاله به تکرار آن‌ها نمی‌پردازیم. به جای آن روی موضوع اصلی این راهنما یعنی نشان دادن کاربردهای رایج نادرست لامبداها می‌پردازیم. بنابراین هر زمان که از لامبدا در یکی از کاربردهای فهرست شده زیر استفاده نکنید، ‌یعنی از آن‌ها استفاده صحیحی کرده‌اید.

1. اختراع مجدد چرخ

نخستین کاربرد نادرست لامبدا، نادیده گرفتن تابع‌های داخلی پایتون است. برای مثال مجدداً به تابع داخلی ()sorted در پایتون اشاره می‌کنیم. فرض کنید که یک لیست از رشته‌ها دارید و می‌خواهید آن‌ها را با استفاده از طولشان مرتب‌سازی کنید. قطعاً تابع لامبدای lambda x: len(x) به این منظور مناسب است، اما آیا می‌توان مستقیماً از تابع داخلی ()len استفاده کرد؟

1>>> pets = ['dog', 'turtle', 'bird', 'fish', 'kitty']
2>>> sorted(pets, key=lambda x: len(x))
3['dog', 'bird', 'fish', 'kitty', 'turtle']
4# The built-in len() function
5>>> sorted(pets, key=len)
6['dog', 'bird', 'fish', 'kitty', 'turtle']

در ادامه مثال دیگری می‌بینید که شامل استفاده از تابع ()max است:

1>>> number_tuples = [(4, 5, 7), (3, 1, 2), (9, 4, 1)]
2>>> sorted(number_tuples, key=lambda x: max(x))
3[(3, 1, 2), (4, 5, 7), (9, 4, 1)]
4# The built-in max() function
5>>> sorted(number_tuples, key=max)
6[(3, 1, 2), (4, 5, 7), (9, 4, 1)]

نخستین رویه مناسب چنین است: پیش از نوشتن تابع لامبدا، به استفاده از توابع داخلی پایتون فکر کنید.

2. انتساب به متغیر

در برخی راهنماها اشاره شده است که می‌توان لامبداها را به متغیرها انتساب داد. در این موارد صرفاً منظور آن است که لامبدا هم تابع هستند، اما متأسفانه برای افراد مبتدی فرض می‌کنند که این یک رویه مناسب است و لامبداها در واقع یک روش آسان‌تر برای اعلان یک تابع کوتاه هستند. قطعه کد زیر چنین کاربرد نادرستی را نشان می‌دهد:

>>> divide_two_numbers = lambda x, y: x / y
>>> divide_two_numbers(4, 5)
0.8

این کاربرد نادرستی است، زیرا اگر نکته‌ای که در بخش قبل اشاره کردیم را به خاطر بیاورید، گفتیم که لامبداها برای این طراحی شده‌اند که فقط یک بار استفاده شوند و از این رو دلیلی برای انتساب آن‌ها به یک متغیر وجود ندارد. اگر لازم است کارکردی مجدداً استفاده شود، باید از کلیدواژه def برای اعلان یک تابع معمولی مانند زیر استفاده کنیم.

>>> def divide_two_numbers_fun(x,y):
... return x / y
...
>>> divide_two_numbers_fun(7, 8)
0.875

اگر فکر می‌کنید که داشتن دو خط کد برای این تابع ساده جالب نیست، می‌توانید آن را در یک خط به صورت زیر بازنویسی کنید که عملکرد مشابهی دارد:

def divide_two_numbers_fun(x,y): return x / y

دلیل عمده برای اجتناب از انتساب یک لامبدا به یک متغیر به خاطر مقاصد دیباگ کردن و نگهداری به خصوص در محیط پروداکشن و کار تیمی است. در ادامه این موضوع را با یک مثال ساده توضیح می‌دهیم. در موقعیت‌های واقعی مسائل ممکن است بسیار پیچیده‌تر از این باشد:

1>>> divide_two_numbers(3, 0)
2Traceback (most recent call last):
3  File "<stdin>", line 1, in <module>
4  File "<stdin>", line 1, in <lambda>
5ZeroDivisionError: division by zero
6>>> divide_two_numbers_fun(3, 0)
7Traceback (most recent call last):
8  File "<stdin>", line 1, in <module>
9  File "<stdin>", line 1, in divide_two_numbers_fun
10ZeroDivisionError: division by zero

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

دلیل این که نام تابع نمایش نیافته است، این است که لامبداها تابع‌های بی‌نام هستند و همگی آن‌ها یک نام مشترک به صورت <lambda> دارند. در صورتی که تعداد لامبداها زیاد باشد، این رویه موجب ایجاد مشکل زیادی می‌شود.

رویه مناسب دوم چنین است: در مواردی که تابع باید چند بار استفاده شود، ‌به جای لامبدا از تابع معمولی استفاده کنید.

3. استفاده مکرر نامناسب به همراه تابع‌های مرتبه بالا

زمانی که از تابع‌های مرتبه بالا صحبت می‌کنیم، منظور ما تابع‌هایی هستند که از طریق گرفتن تابع دیگر به صوت آرگومان یا بازگشت دادن یک تابع، روی این توابع کاری انجام می‌دهند.

تابع‌های مرتبط با موضوع این مقاله شامل ()map()، filter و ()reduce هستند که کمابیش در اغلب راهنماها روی لامبداها استفاده می‌شوند. اما این امر موجب کاربرد نامناسب خاصی از لامبداها، همراه با تابع‌های مرتبط بالا می‌شود.

به منظور نمایش این کاربرد نامناسب از تابع ()map در این راهنما استفاده کردیم، ‌اما همین فلسفه در مورد تابع‌های مرتبه بالای دیگر نیز صدق می‌کند. فرض کنید لیستی از اعداد صحیح دارید و می‌خواهید لیستی از مربع این اعداد به دست آورید. با استفاده همزمان از لامبدا و تابع ()map به صورت زیر عمل می‌کنید.

بدین ترتیب یک تکرارکننده به دست می‌آید که شیء map از تابع ()map است و سپس آن را به یک لیست تبدیل می‌کنیم. در این حالت باید تابع ()list را روی این تکرارکننده فراخوانی کنیم.

>>> numbers = [1, 2, 3, 5, 8]
>>> squares = list(map(lambda x: x * x, numbers))
>>> squares
[1, 4, 9, 25, 64]

در عمل، ‌همین کارکرد می‌تواند به سهولت با استفاده از «خلاصه‌سازی لیست» (List Comprehensions) نیز به دست آید و دیگر به تابع مرتبه بالا یا لامبدا نیازی نداریم. این کد خوانایی و انسجام بیشتری هم دارد. بدیهی است که یادگیری خلاصه‌سازی لیست خود یک مهارت مستقل پایتونی محسوب می‌شود.

>>> numbers = [1, 2, 3, 5, 8]
>>> squares = [x * x for x in numbers]
>>> squares
[1, 4, 9, 25, 64]

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

4. استفاده از عبارت‌های پیچیده

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

فرض کنید که لیستی از رشته‌ها دارید و می‌خواهید آن‌ها را با استفاده از یک شرایط عجیب یعنی تعداد حروف صدادار متمایز در کلمه مرتب‌سازی کنید. با استفاده از لامبدا در تابع ()sorted کار به صورت زیر خواهد بود:

>>> texts = ['iiiii', 'bag', 'beto', 'blackboard', 'sequoia']
>>> sorted(texts, key=lambda x: len(set([l for l in list(x) if l in ['a','e','i','o','u']])))
['iiiii', 'bag', 'beto', 'blackboard', 'sequoia']

این کد مطابق انتظار عمل می‌کند، اما قطعاً خواناترین کد ممکن نیست. حال به کد زیر توجه کنید:

1>>> texts = ['iiiii', 'bag', 'beto', 'blackboard', 'sequoia']
2>>> def number_distinct_vowels(x):
3...     vowels = ['a', 'e', 'i', 'o', 'u']
4...     vowels_only = [l for l in list(x) if l in vowels]
5...     distinct_vowels = set(vowels_only)
6...     return len(distinct_vowels)
7... 
8>>> sorted(texts, key=number_distinct_vowels)
9['iiiii', 'bag', 'beto', 'blackboard', 'sequoia']

بدیهی است که باید چند خط بیشتر کد بنویسیم، اما خوانایی کد اخیر بالاتر است.

رویه مناسب شماره 4 چنین است: در صورتی که عبارت لامبدا، پیچیده است، ‌از تابع معمولی استفاده کنید.

سخن پایانی

لامبدا همواره یکی از موضوعات دشوار برای افراد تازه‌کار در زبان پایتون بوده است و اغلب به هر قیمت شده می‌خواهند که از استفاده از آن خودداری کنند. پس از مدتی که ترس این افراد می‌ریزد، ‌شروع به یادگیری لامبدا می‌کنند و متوجه می‌شوند که اصلاً موضوع دشواری محسوب نمی‌شود. در این زمان شروع به استفاده از لامبدا می‌کنند، ‌اما متأسفانه گاهی اوقات در این زمینه افراط می‌کنند که منجر به کاربردهای نامناسب مختلفی می‌شود که در این مقاله به آن‌ها اشاره کردیم.

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

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

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