وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

درک کلمه کلیدی “بازده” پایتون

0 5
زمان لازم برای مطالعه: 6 دقیقه


این 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 استفاده می شود.

پیشنهاد می‌کنیم بخوانید:  باز کردن چندین فایل با استفاده از "with open" در پایتون

حالا کی 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 تکرار کننده فراخوانی می شود تا زمانی که تمام آیتم های مولد دوباره تکرار شوند.

عملکرد بهینه شده

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

پیشنهاد می‌کنیم بخوانید:  چگونه یک فایل .gz را با استفاده از پایتون از حالت فشرده خارج کنیم

در مثال های قبلی تفاوت عملکرد یک لیست ساده و مولد قابل مشاهده نبود زیرا اندازه لیست بسیار کوچک بود. در این بخش نمونه هایی را بررسی می کنیم که در آنها می توانیم بین عملکرد لیست ها و مولدها تمایز قائل شویم.

در کد زیر تابعی می نویسیم که لیستی حاوی 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

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید