Python حرفه‌ای: Lambda Expression

Professional Python: Lambda Expression

19 اسفند 1399
درسنامه درس 16 از سری پایتون حرفه‌ای
Python حرفه ای: Lambda Expression (قسمت 16)

lambda function چیست؟

کلمه lambda یازدهمین حرف الفبا در زبان یونانی است که به شکل λ می باشد.

lambda expression ها یا lambda function ها در زبان پایتون، توابع ناشناس (anonymous) و یک بار مصرفی هستند که فقط و فقط یک بار اجرا می شوند. از همین تعریف می توانیم دو خصوصیت lambda expression ها را حدس بزنیم:

  • فقط یک بار از این دسته از توابع استفاده می کنیم.
  • توابعی ناشناخته (anonymous) هستند.

در صورتی که نمی دانید، توابع ناشناخته توابعی هستند که هیچ نامی ندارند! در هنگام تعریف توابع عادی، همیشه از کلید واژه def استفاده کرده و یک نام را برای توابع خود انتخاب می کردیم اما در توابع anonymous از روش دیگری استفاده می کنیم. ساختار توابع lambda به شکل زیر است:

# lambda param: some action here on the param

یعنی ابتدا کلمه lambda را نوشته و سپس یک پارامتر را به آن می دهیم. در مرحله بعدی عملیاتی را می نویسیم که روی آن پارامتری اجرا می شود (همان expression ما). برای درک بهتر باید یک تابع lambda واقعی بنویسیم. اگر یادتان باشد در جلسه قبل چنین کدی را داشتیم:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]







def multiply_by2(item):

    return item * 2







new_list = map(multiply_by2, my_list)




print(list(new_list))

print(my_list)

این کد لیست ما را گرفته و تمام اعضای آن را در ۲ ضرب می کرد. اگر بخواهیم همین کد را با توابع lambda بنویسیم، می گوییم:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]







new_list = map(lambda item: item * 2, my_list)




print(list(new_list))

print(my_list)

به عبارتی ما می دانیم که اگر تابع map اجرا شود، فقط یک بار به اولین آرگومان خود (تابع ارسال شده) نیاز خواهد داشت، بنابراین چرا یک تابع اضافی تعریف کنیم که مموری ما را اشغال کند؟ من برای آرگومان اول از کلمه lambda استفاده کرده ام، سپس مثل یک تابع ساده نام پارامتر خود را مشخص می کنیم. من نام item را انتخاب کرده ام. در نهایت باید از علامت دو نقطه (:) استفاده کرده و expression خود را بنویسید که در اینجا item * 2 است. با اجرای کد بالا نتیجه زیر را می گیرید:

[2, 4, 6, 8, 10, 12, 14, 16, 18, 0]

[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

بنابراین همه چیز مثل قبل کار می کند با این تفاوت که این بار کد های ما بسیار خلاصه تر و تمیز تر هستند.حالا از شما سوالی دارم؛ دو عبارت lambda expression و lambda function معمولا به جای یکدیگر به کار می روند. به نظر شما دلیل این موضوع چیست؟ چرا من در چند خط بالاتر گفتم که باید expression خود را بنویسید؟ مسئله اینجاست که توابع lambda می توانند فقط و فقط یک expression را داشته باشند بنابراین نمی توانند کارهای پیچیده را انجام بدهند.

در صورتی که بخواهیم مثال تابع filter از جلسه قبل را با تابع lambda بنویسیم می گوییم:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]







new_list = filter(lambda item: item % 2 != 0, my_list)




print(list(new_list))

print(my_list)

با اجرای این کد نتیجه زیر را می گیریم:

[1, 3, 5, 7, 9]

[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

بنابراین همه چیز به خوبی کار می کند. در نهایت یک مثال نیز برای تابع reduce خود از جلسه قبل می نویسیم:

from functools import reduce




my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]







new_list = reduce(lambda acc, item: acc + item, my_list)




print(new_list)

print(my_list)

ما برای تابع reduce به دو پارامتر نیاز داشتیم (acc و item) بنابراین باید هر دو را در lambda دریافت کنیم. با اجرای کد بالا نتیجه زیر را می گیریم:

45

[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

دقیقا همان نتیجه ای که در جلسه قبل دریافت کرده بودیم.

تمرین: تابع lambda

برای درک بهتر توابع lambda از شما می خواهم این تمرین را خودتان انجام بدهید. این تمرین شامل دو بخش است. در بخش اول باید فرض کنید لیست ساده ای (مثلا سه عضو) داریم. من از شما می خواهم تابع lambda ساده ای بنویسید که اعضای این لیست را به توان ۲ برساند. در بخش دوم لیست زیر را به شما می دهم:

some_list = [(0,2), (4,3), (10, -1), (9,9)]

همانطور که می بینید اعضای این لیست tuple هستند. من از شما می خواهم این tuple ها را بر اساس عضو دوم آن ها مرتب کنید. یعنی tuple سوم (عدد ۱۰ و ۱-) اولین tuple در لیست جدید باشد. چرا؟ به دلیل اینکه عضوِ دومِ آن (عدد ۱-) کوچکترین عدد بین اعضای دومِ tuple های این لیست است. سعی کنید خودتان به پاسخ هر دو سوال برسید. من در ادامه پاسخ خودم را برایتان می نویسم.

برای سوال اول می توانیم به سادگی از روش زیر استفاده کنیم:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]




new_list = list(map(lambda num: num ** 2, my_list))

print(new_list)

همانطور که می بینید انجام این کار بسیار ساده است. من تابع map را صدا زده ام و از اپراتور توان (**) برای به توان رساندن تک تک اعضای لیست خودم استفاده کرده ام.

برای حل سوال دوم نیاز به زمان گذاشتن و تفکر دارید چرا که کمی گیج کننده است. اگر یادتان باشد ما متدی به نام sort را برای لیست ها داریم که لیست را مرتب می کرد. آیا می توانیم از آن استفاده کنیم؟

some_list = [(0, 2), (4, 3), (10, -1), (9, 9)]




some_list.sort()

print(some_list)

با اجرای کد بالا نتیجه زیر را می گیریم:

[(0, 2), (4, 3), (9, 9), (10, -1)]

همانطور که می بینید sort این کار را بر اساس عضو اول tuple انجام داده است. بنابراین استفاده از آن به صورت ساده ممکن نیست. اگر یادتان باشد در جلسات قبل مبحث آرگومان های پیش فرض را بررسی کردیم و در همان قسمت برایتان توضیح دادم که اگر بخواهیم مقدار پیش فرض یک آرگومان را تغییر بدهیم از آرگومان های کلیدواژه ای استفاده می کنیم. مثالی که در آن جلسه حل کردیم، آرگومان end در تابع print بود:

print("something to print", end="")

مقدار end به صورت پیشفرض n\ است که یعنی به خط بعدی می رویم اما من در اینجا از یک رشته خالی استفاده کرده ام. این کد بخشی از راه حل ما برای نمایان کردن درخت در یکی از تمرینات بود. حالا می توانیم از همین مفهوم برای متد sort استفاده کرده و آن را با توابع lambda ترکیب کنیم:

some_list = [(0, 2), (4, 3), (10, -1), (9, 9)]




some_list.sort(key=lambda x: x[1])

print(some_list)

متد sort آرگومان پیش فرضی به نام key دارد که مشخص کننده کلیدی است که عملیات مرتب سازی بر اساس آن صورت می گیرد. از طرفی برای مرتب سازی مقادیر یک لیست باید روی این مقادیر گردش کنیم (تابع sort در پس زمینه این کار را انجام می دهد) بنابراین تابع lambda خود را به آن داده ایم. x هر عضو از لیست است که در این مثال یک tuple می باشد بنابراین دسترسی به ایندکس اول (عضو دوم) کار ما را ساده می کند. با اجرای کد بالا نتیجه به شکل زیر نمایش داده می شود:

[(10, -1), (0, 2), (4, 3), (9, 9)]

list comprehension چیست؟

در زبان پایتون قابلیت خاصی به نام comprehension وجود دارد که روند کدنویسی برای ما را بسیار راحت تر می کند. در زبان پایتون سه دسته comprehension مختلف وجود دارد:

  • list comprehension
  • set comprehension
  • dictionary comprehension

comprehension ها روشی سریع برای ساخت سه نوع داده بالا (لیست، دیکشنری و set) بدون گردش و append هستند. آیا متوجه منظور من شدید؟ در حالت عادی برای ساخت یک لیست از یک رشته ساده باید از حلقه ها استفاده کنیم:

some_string = "ROXO ACADEMY"

new_list = []




for item in some_string:

    new_list.append(item)




print(new_list)

با اجرای این کد نتیجه زیر را می گیریم:

['R', 'O', 'X', 'O', ' ', 'A', 'C', 'A', 'D', 'E', 'M', 'Y']

ما برای انجام تبدیل ساده ای مثل تبدیل بالا مجبور به نوشتن یک حلقه for شدیم اما comprehension ها به شما اجازه می دهند بسیار سریع تر از این کارتان را انجام بدهید:

some_string = "ROXO ACADEMY"




new_list = [char for char in some_string]




print(new_list)

char یک نام دلخواه برای پارامتر این دستور است، من char را انتخاب کرده ام اما شما می توانید از هر نام دیگری استفاده کنید. نکته اینجاست که هر دو char با هم همنام باشند. char اول یک expression است و در این مثال نام متغیری است که توسط ما ساخته شده و به لیست پاس داده می شود و char دوم نیز تک تک اعضای some_string است. یکسان بودن نام این دو به همین جهت است که char (تک حرف از رشته) برابر char (متغیر پاس داده شده به لیست) باشد. با انجام این کار و اجرای این کد نتیجه زیر را می گیریم:

['R', 'O', 'X', 'O', ' ', 'A', 'C', 'A', 'D', 'E', 'M', 'Y']

همانطور که می بینید انجام این کار بسیار ساده تر از نوشتن یک حلقه for است.

تمرین: ساخت لیست با comprehension

برای درک بهتر comprehension ها چندین تمرین مختلف را در این قسمت انجام خواهیم داد. برای شروع تمرین ساده ای داریم که دو قسمت مختلف دارد: در قسمت اول از شما می خواهم با استفاده از comprehension ها یک لیست بسازید که اعداد صفر تا ۹۹ را در خود داشته باشد. در قسمت دوم از شما می خواهم همین کار را انجام بدهید اما این بار تمام اعداد لیست ۲ برابر شده باشند. مثل همیشه بهتر است خودتان به سوال پاسخ بدهید یا حداقل روی آن فکر کنید و سپس به پاسخ من نگاه کنید.

برای قسمت اول سوال می توانیم از range استفاده کنیم:

new_list = [num for num in range(0, 100)]




print(new_list)

با اجرای این کد نتیجه زیر را می گیریم:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

برای حل قسمت دوم سوال نیز باید به نکته خاصی توجه کنید. همانطور که گفتم قسمت اول comprehension یک expression است بنابراین می توانیم یک expression ساده را در آن بنویسیم (دقیقا مانند توابع lambda):

new_list = [num * 2 for num in range(0, 100)]




print(new_list)

با اجرای این کد نتیجه زیر را می گیریم:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198]

برای سوال بعدی از شما می خواهم به جای دو برابر کردن اعداد آن ها را به توان ۲ رسانده و سپس از بین آن ها فقط اعداد فرد را نگه دارید و اعداد زوج را دور بیندازید. احتمالا نتوانید به این سوال پاسخ بدهید چرا که هنوز با قسمت سوم comprehension ها آشنا نشده اید (این سوال برای افراد مبتدی نیست) اما هنوز هم فکر روی آن باعث قوی تر شدن قدرت تحلیل شما می شود.

new_list = [num ** 2 for num in range(0, 100) if num % 2 != 0]




print(new_list)

حتما متوجه شده اید که comprehension ها سه قسمت دارند: قسمت اول یا expression (همان  num ** 2) قسمت دوم یا حلقه (همان for num in range(0, 100)) قسمت سوم یا شرط ( if num % 2 != 0). با اجرای این کد نتیجه زیر را می گیریم:

[1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625, 729, 841, 961, 1089, 1225, 1369, 1521, 1681, 1849, 2025, 2209, 2401, 2601, 2809, 3025, 3249, 3481, 3721, 3969, 4225, 4489, 4761, 5041, 5329, 5625, 5929, 6241, 6561, 6889, 7225, 7569, 7921, 8281, 8649, 9025, 9409, 9801]

آشنایی با dictionary comprehension

در این بخش می خواهیم در رابطه با set comprehension و dictionary comprehension صحبت کنیم. استفاده از set comprehension بسیار ساده است و تنها تفاوت آن با list comprehension استفاده از علامت {} به جای [] است. به طور مثال:

some_string = "ROXO ACADEMY"




list1 = {char for char in some_string}




list2 = {num for num in range(0, 100)}




list3 = {num * 2 for num in range(0, 100)}




list4 = {num ** 2 for num in range(0, 100) if num % 2 != 0}




print(list1)

من مثال های مختلفی را برایتان نوشته ام اما تک تک آن ها را بررسی نمی کنیم بلکه فقط یک نمونه (list1) را برای تست انتخاب کرده ام. با اجرای کد بالا نتیجه زیر را می گیریم:

{'C', 'R', 'A', 'E', 'Y', 'O', ' ', 'M', 'D', 'X'}

آیا می دانید چرا نتیجه به این شکل عجیب و غریب در آمده است؟ قبلا برایتان توضیح داده بودم که set ها فقط مقادیر یکتا و غیر تکراری را ذخیره می کنند بنابراین مقادیر تکراری حذف شده است. از طرفی هم توضیح دادم که set ها و dictionary ها «نامنظم» هستند یا به عبارتی ترتیب خاصی در مموری سیستم نداشته و در آن پراکنده هستند بنابراین برخلاف لیست ها ترتیبی در آن ها رعایت نمی شود. این دو عامل در کنار هم باعث تولید نتیجه بالا شده است.

تنها موردی که باقی می ماند dictionary comprehension است. ما می دانیم که دیکشنری ها جفت های key:value هستند بنابراین چطور می توانیم این قابلیت را در آن ها پیاده سازی کنیم؟

some_dictionary = {'a': 1, 'b': 2}




my_dict = {key: value**2 for key, value in some_dictionary.items()}




print(my_dict)

من در اینجا از یک دیکشنری برای ساخت یک دیکشنری دیگر استفاده کرده ام چرا که دیکشنری ها دو مقدار key:value را دارند. قسمت اول کد بالا (key: value**2) نام کلید و مقدار آن در دیکشنری ما است. شما می توانید به جای key:value هر نام دیگری را انتخاب کنید (مثلا k: v **2). من مقدار کلید ها را در این کد به توان ۲ رسانده ام. در قسمت دوم این کد (for key, value in some_dictionary.items) شاهد یک حلقه ساده هستیم که آیتم های دیکشنری ما را گرفته و آن ها را به key و value می دهد. با اجرای این کد نتیجه زیر را می گیریم:

{'a': 1, 'b': 4}

در صورتی که می خواستیم فقط اعداد زوج را نگه داریم، باید قسمت سوم یا همان شرط if را اضافه کنیم:

some_dictionary = {'a': 1, 'b': 2}




my_dict = {key: value**2 for key, value in some_dictionary.items() if value % 2 == 0}




print(my_dict)

با اجرای این کد نتیجه زیر را می گیریم:

{'b': 4}

در صورتی که به جای دیکشنری یک لیست داشته باشیم چطور؟ یعنی بخواهیم از یک لیست یک دیکشنری ایجاد کنیم؟ مسئله اینجاست که ما برای ساخت دیکشنری به دو مقدار برای key و value نیاز داریم بنابراین باید از هر طریقی که شده دو مقدار را ایجاد کنیم:

some_list = [1, 2, 3]




my_dict = {num: num*2 for num in some_list}




print(my_dict)

نتیجه:

{1: 2, 2: 4, 3: 6}

امیدوارم از این قسمت لذت برده باشید. در جلسه بعدی به سراغ مبحث decorator ها می رویم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.