از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
درک کلمه کلیدی “بازده” پایتون
سرفصلهای مطلب
این yield
کلمه کلیدی در پایتون برای ایجاد ژنراتور استفاده می شود. ژنراتور نوعی مجموعه است که اقلام تولید می کند روی-the-fly و فقط یک بار قابل تکرار است. با استفاده از ژنراتورها می توانید عملکرد برنامه خود را بهبود بخشید و حافظه کمتری را در مقایسه با مجموعه های معمولی مصرف کنید، بنابراین عملکرد خوبی را افزایش می دهد.
در این مقاله روش استفاده از آن را توضیح خواهیم داد yield
کلمه کلیدی در پایتون و کاری که دقیقا انجام می دهد. اما ابتدا بیایید تفاوت بین یک مجموعه لیست ساده و مولد را بررسی کنیم و سپس خواهیم دید که چگونه yield
می توان برای ایجاد ژنراتورهای پیچیده تر استفاده کرد.
تفاوت بین لیست و ژنراتور
در اسکریپت زیر هم یک لیست و هم یک مولد ایجاد می کنیم و سعی می کنیم تفاوت آنها را ببینیم. ابتدا یک لیست ساده ایجاد می کنیم و نوع آن را بررسی می کنیم:
squared_list = (x**2 for x in range(5))
type(squared_list)
هنگام اجرای این کد باید ببینید که نوع نمایش داده شده “list” خواهد بود.
حالا بیایید تمام موارد موجود در را تکرار کنیم squared_list
.
for number in squared_list:
print(number)
اسکریپت بالا نتایج زیر را ایجاد خواهد کرد:
$ python squared_list.py
0
1
4
9
16
حالا بیایید یک ژنراتور ایجاد کنیم و دقیقاً همان کار را انجام دهیم:
squared_gen = (x**2 for x in range(5))
type(squared_gen)
برای ایجاد یک ژنراتور، دقیقاً همانطور که با درک لیست شروع می کنید، شروع می کنید، اما در عوض باید به جای پرانتز از پرانتز استفاده کنید. اسکریپت بالا “generator” را به عنوان نوع برای نمایش می دهد squared_gen
متغیر. حالا بیایید با استفاده از یک حلقه for روی ژنراتور تکرار کنیم.
for number in squared_gen:
print(number)
خروجی خواهد بود:
$ python squared_gen.py
0
1
4
9
16
خروجی همان لیست است. خوب فرقش چیست؟ یکی از تفاوت های اصلی در روش ذخیره سازی عناصر لیست و ژنراتورها در حافظه نهفته است. لیست ها همه عناصر را به یکباره در حافظه ذخیره می کنند، در حالی که مولدها هر آیتم را “ایجاد می کنند”. روی-the-fly، آن را نمایش می دهد و سپس به عنصر بعدی می رود و عنصر قبلی را از حافظه حذف می کند.
یکی از راههای تأیید این موضوع، بررسی طول لیست و مولدهایی است که به تازگی ایجاد کردهایم. این len(squared_list)
5 در حالی که بازگشت len(squared_gen)
خطایی ایجاد می کند که یک ژنراتور طول ندارد. همچنین، می توانید هر چند بار که می خواهید روی یک لیست تکرار کنید، اما فقط یک بار می توانید روی یک ژنراتور تکرار کنید. برای تکرار مجدد، باید دوباره ژنراتور را ایجاد کنید.
با استفاده از کلمه کلیدی Yield
اکنون ما تفاوت بین مجموعه های ساده و ژنراتورها را می دانیم، اجازه دهید ببینیم چگونه yield
می تواند به ما در تعریف یک ژنراتور کمک کند.
در مثالهای قبلی، یک مولد به طور ضمنی با استفاده از سبک درک لیست ایجاد کردیم. با این حال در سناریوهای پیچیدهتر میتوانیم توابعی ایجاد کنیم که یک ژنراتور را برمیگردانند. این yield
کلمه کلیدی، بر خلاف return
عبارت، برای تبدیل یک تابع معمولی پایتون به یک ژنراتور استفاده می شود. این به عنوان جایگزینی برای بازگرداندن کل لیست به طور همزمان استفاده می شود. این موضوع مجددا با کمک چند مثال ساده توضیح داده خواهد شد.
دوباره، اجازه دهید ابتدا ببینیم در صورت عدم استفاده از تابع ما چه چیزی را برمی گرداند yield
کلمه کلیدی. اسکریپت زیر را اجرا کنید:
def cube_numbers(nums):
cube_list =()
for i in nums:
cube_list.append(i**3)
return cube_list
cubes = cube_numbers((1, 2, 3, 4, 5))
print(cubes)
در این اسکریپت یک تابع cube_numbers
ایجاد می شود که لیستی از اعداد را می پذیرد، مکعب های آنها را می گیرد و کل لیست را به تماس گیرنده برمی گرداند. هنگامی که این تابع فراخوانی می شود، لیستی از مکعب ها برگردانده شده و در آن ذخیره می شود cubes
متغیر. از خروجی می توانید ببینید که داده های برگشتی در واقع یک لیست کامل هستند:
$ python cubes_list.py
(1, 8, 27, 64, 125)
حال، به جای برگرداندن یک لیست، بیایید اسکریپت بالا را طوری تغییر دهیم که یک ژنراتور را برگرداند.
def cube_numbers(nums):
for i in nums:
yield(i**3)
cubes = cube_numbers((1, 2, 3, 4, 5))
print(cubes)
در اسکریپت بالا، cube_numbers
تابع به جای لیست اعداد مکعب، یک ژنراتور را برمی گرداند. ایجاد یک ژنراتور با استفاده از آن بسیار ساده است yield
کلمه کلیدی. در اینجا ما نیازی به موقت نداریم cube_list
متغیر برای ذخیره عدد مکعبی، بنابراین حتی ما cube_numbers
روش ساده تر است بازهم نه return
بیانیه مورد نیاز است، اما در عوض yield
کلمه کلیدی برای برگرداندن عدد مکعب شده در داخل حلقه for استفاده می شود.
حالا کی cube_number
تابع فراخوانی می شود، یک ژنراتور برگردانده می شود که می توانیم با اجرای کد آن را تأیید کنیم:
$ python cubes_gen.py
<generator object cube_numbers at 0x1087f1230>
با وجود اینکه ما به آن زنگ زدیم cube_numbers
تابع، در واقع در این نقطه از زمان اجرا نمی شود و هنوز هیچ آیتمی در حافظه ذخیره نشده است.
برای اجرای تابع و در نتیجه مورد بعدی از ژنراتور، از داخلی استفاده می کنیم next
روش. وقتی به next
اشاره گر روی ژنراتور برای اولین بار، تابع تا زمانی اجرا می شود yield
کلمه کلیدی مواجه می شود. یک بار yield
مقدار ارسال شده به آن به تابع فراخوانی برگردانده می شود و تابع مولد در حالت فعلی متوقف می شود.
در اینجا روش دریافت مقدار از مولد خود آمده است:
next(cubes)
تابع فوق “1” را برمی گرداند. حالا وقتی زنگ میزنی next
از نو روی ژنراتور، cube_numbers
عملکرد از جایی که قبلاً در آن متوقف شده بود، اجرا را از سر می گیرد yield
. عملکرد تا زمانی که پیدا کند به اجرا ادامه خواهد داد yield
از نو. این next
تابع مقدار مکعب شده را یک به یک باز می گرداند تا زمانی که همه مقادیر موجود در لیست تکرار شوند.
هنگامی که تمام مقادیر تکرار می شوند next
تابع a را پرتاب می کند توقف تکرار استثنا. ذکر این نکته ضروری است که cubes
ژنراتور هیچ یک از این موارد را در حافظه ذخیره نمی کند، بلکه مقادیر مکعبی در زمان اجرا محاسبه می شوند، برگردانده می شوند و فراموش می شوند. تنها حافظه اضافی مورد استفاده، داده های وضعیت خود ژنراتور است که معمولاً بسیار کمتر از یک لیست بزرگ است. این باعث می شود که ژنراتورها برای کارهای با حافظه فشرده ایده آل باشند.
به جای اینکه همیشه مجبور به استفاده از next
iterator، در عوض می توانید از یک حلقه “for” برای تکرار بر روی مقادیر یک ژنراتور استفاده کنید. هنگام استفاده از حلقه “for”، در پشت صحنه next
تکرار کننده فراخوانی می شود تا زمانی که تمام آیتم های مولد دوباره تکرار شوند.
عملکرد بهینه شده
همانطور که قبلاً ذکر شد، ژنراتورها برای کارهایی که حافظه فشرده دارند بسیار مفید هستند، زیرا آنها نیازی به ذخیره همه موارد مجموعه در حافظه ندارند، بلکه آنها موارد را تولید می کنند. روی مگس و به محض اینکه تکرار کننده به آیتم بعدی می رود آن را دور می اندازد.
در مثال های قبلی تفاوت عملکرد یک لیست ساده و مولد قابل مشاهده نبود زیرا اندازه لیست بسیار کوچک بود. در این بخش نمونه هایی را بررسی می کنیم که در آنها می توانیم بین عملکرد لیست ها و مولدها تمایز قائل شویم.
در کد زیر تابعی می نویسیم که لیستی حاوی 1 میلیون ساختگی را برمی گرداند car
اشیاء. ما حافظه اشغال شده توسط را محاسبه خواهیم کرد process قبل و بعد از فراخوانی تابع (که لیست را ایجاد می کند).
به کد زیر دقت کنید:
import time
import random
import os
import psutil
car_names = ('Audi', 'Toyota', 'Renault', 'Nissan', 'Honda', 'Suzuki')
colors = ('Black', 'Blue', 'Red', 'White', 'Yellow')
def car_list(cars):
all_cars = ()
for i in range(cars):
car = {
'id': i,
'name': random.choice(car_names),
'color': random.choice(colors)
}
all_cars.append(car)
return all_cars
process = psutil.Process(os.getpid())
print('Memory before list is created: ' + str(process.memory_info().rss/1000000))
t1 = time.clock()
cars = car_list(1000000)
t2 = time.clock()
process = psutil.Process(os.getpid())
print('Memory after list is created: ' + str(process.memory_info().rss/1000000))
print('Took {} seconds'.format(t2-t1))
توجه داشته باشید: ممکن است مجبور شوید pip install psutil
تا این کد کار کند روی ماشین شما
در دستگاه روی که کد اجرا شد، نتایج زیر به دست آمد (ممکن است نتایج شما کمی متفاوت به نظر برسد):
$ python perf_list.py
Memory before list is created: 8
Memory after list is created: 334
Took 1.584018 seconds
قبل از ایجاد لیست process حافظه بود 8 مگابایت، و پس از ایجاد لیستی با 1 میلیون مورد، حافظه اشغال شده به آن پرید 334 مگابایت. همچنین مدت زمان ایجاد لیست 1.58 ثانیه بود.
حالا بیایید موارد بالا را تکرار کنیم process اما لیست را با ژنراتور جایگزین کنید. اسکریپت زیر را اجرا کنید:
import time
import random
import os
import psutil
car_names = ('Audi', 'Toyota', 'Renault', 'Nissan', 'Honda', 'Suzuki')
colors = ('Black', 'Blue', 'Red', 'White', 'Yellow')
def car_list_gen(cars):
for i in range(cars):
car = {
'id':i,
'name':random.choice(car_names),
'color':random.choice(colors)
}
yield car
process = psutil.Process(os.getpid())
print('Memory before list is created: ' + str(process.memory_info().rss/1000000))
t1 = time.clock()
for car in car_list_gen(1000000):
pass
t2 = time.clock()
process = psutil.Process(os.getpid())
print('Memory after list is created: ' + str(process.memory_info().rss/1000000))
print('Took {} seconds'.format(t2-t1))
در اینجا باید از آن استفاده کنیم for car in car_list_gen(1000000)
حلقه برای اطمینان از اینکه همه 1000000 ماشین واقعا تولید شده است.
با اجرای اسکریپت فوق نتایج زیر بدست آمد:
$ python perf_gen.py
Memory before list is created: 8
Memory after list is created: 40
Took 1.365244 seconds
از خروجی مشاهده می کنید که با استفاده از ژنراتورها تفاوت حافظه بسیار کمتر از قبل است (از 8 مگابایت به 40 مگابایت) از آنجایی که ژنراتورها موارد را در حافظه ذخیره نمی کنند. علاوه بر این، زمان صرف شده برای فراخوانی تابع مولد کمی سریعتر و همچنین در 1.37 ثانیه بود که حدود 14٪ سریعتر از ایجاد لیست است.
نتیجه
امیدواریم از این مقاله شما درک بهتری از آن داشته باشید yield
کلمه کلیدی، از جمله روش استفاده از آن، استفاده از آن و اینکه چرا می خواهید از آن استفاده کنید. ژنراتورهای پایتون یک راه عالی برای بهبود عملکرد برنامه های شما هستند و استفاده از آنها بسیار ساده است، اما درک زمان استفاده از آنها چالش بسیاری از برنامه نویسان تازه کار است.
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-28 22:21:12