ژنراتورها و Yield در پایتون: مفاهیم متوسط
در پایتون، ژنراتورها ابزاری قدرتمند برای ایجاد توالیهای دادهای هستند که به صورت تنبل (Lazy) ارزیابی میشوند. این بدان معناست که مقادیر توالی تنها زمانی تولید میشوند که به آنها نیاز باشد، به جای اینکه همه مقادیر به طور همزمان در حافظه ذخیره شوند. این ویژگی ژنراتورها را برای کار با مجموعههای دادهای بزرگ یا توالیهای بینهایت بسیار کارآمد میکند. در این مقاله، به بررسی عمیق ژنراتورها و کلمه کلیدی yield در پایتون میپردازیم.
چرا از ژنراتورها استفاده کنیم؟
قبل از پرداختن به جزئیات پیادهسازی، بیایید نگاهی به مزایای استفاده از ژنراتورها بیندازیم:
- صرفهجویی در حافظه: همانطور که اشاره شد، ژنراتورها مقادیر را به صورت تنبل تولید میکنند. این بدان معناست که تنها یک مقدار در هر زمان در حافظه نگهداری میشود، که میتواند به طور قابل توجهی مصرف حافظه را کاهش دهد، به خصوص هنگام کار با مجموعههای دادهای بزرگ.
- بهبود عملکرد: از آنجایی که مقادیر تنها زمانی تولید میشوند که به آنها نیاز باشد، ژنراتورها میتوانند عملکرد را بهبود بخشند، به خصوص در مواردی که نیازی به پردازش کل توالی نیست.
- قابلیت کار با توالیهای بینهایت: ژنراتورها به شما امکان میدهند توالیهای بینهایت را تعریف کنید، زیرا مقادیر تنها در صورت درخواست تولید میشوند.
- خوانایی کد: در برخی موارد، استفاده از ژنراتورها میتواند کد را سادهتر و خواناتر کند، به خصوص هنگام کار با توالیهای پیچیده.
تفاوت بین ژنراتورها و لیستها
لیستها و ژنراتورها هر دو برای ذخیره و دسترسی به مجموعهای از دادهها استفاده میشوند، اما تفاوتهای کلیدی بین آنها وجود دارد:
- نحوه ذخیرهسازی: لیستها تمام مقادیر را در حافظه ذخیره میکنند، در حالی که ژنراتورها مقادیر را به صورت تنبل تولید میکنند.
- مصرف حافظه: لیستها معمولاً حافظه بیشتری نسبت به ژنراتورها مصرف میکنند، به خصوص برای مجموعههای دادهای بزرگ.
- سرعت: ایجاد یک لیست ممکن است سریعتر از ایجاد یک ژنراتور باشد، اما دسترسی به عناصر یک ژنراتور میتواند سریعتر باشد، به خصوص اگر نیازی به پردازش کل توالی نباشد.
- قابلیت تکرار: لیستها قابل تکرار هستند، اما پس از اتمام تکرار، دیگر نمیتوان آنها را دوباره تکرار کرد. ژنراتورها نیز قابل تکرار هستند، اما هر بار که یک ژنراتور تکرار میشود، از ابتدا شروع به تولید مقادیر میکند.
کلمه کلیدی yield
کلمه کلیدی yield قلب تپنده ژنراتورها در پایتون است. به جای اینکه یک مقدار را به طور کامل برگرداند و تابع را خاتمه دهد، yield مقدار را به فراخواننده برمیگرداند و حالت تابع را حفظ میکند. دفعه بعدی که ژنراتور فراخوانی شود، از جایی که yield قبلاً متوقف شده بود، ادامه میدهد.
مثال ساده
بیایید با یک مثال ساده شروع کنیم:
def my_generator(n):
for i in range(n):
yield i
# استفاده از ژنراتور
for value in my_generator(5):
print(value)
در این مثال، تابع my_generator یک ژنراتور است که اعداد 0 تا 4 را تولید میکند. هر بار که حلقه for یک مقدار را از ژنراتور درخواست میکند، تابع my_generator تا yield i اجرا میشود، مقدار i را برمیگرداند و سپس حالت خود را حفظ میکند. دفعه بعدی که حلقه for یک مقدار را درخواست میکند، تابع my_generator از جایی که متوقف شده بود، ادامه میدهد.
ایجاد ژنراتورها با استفاده از عبارات ژنراتور (Generator Expressions)
علاوه بر تعریف توابع ژنراتور با استفاده از yield، میتوانید از عبارات ژنراتور برای ایجاد ژنراتورها به صورت مختصرتر استفاده کنید. عبارات ژنراتور شبیه به لیستها هستند، اما به جای براکتهای مربع [] از پرانتزهای گرد () استفاده میکنند.
مثال
به عنوان مثال، برای ایجاد یک ژنراتور که مربع اعداد 0 تا 4 را تولید میکند، میتوانید از عبارت ژنراتور زیر استفاده کنید:
squares = (x * x for x in range(5)) # استفاده از ژنراتور for square in squares: print(square)
این عبارت ژنراتور معادل تعریف یک تابع ژنراتور با استفاده از yield است، اما کوتاهتر و خواناتر است.
کاربردهای پیشرفته ژنراتورها
ژنراتورها میتوانند در طیف گستردهای از کاربردها استفاده شوند، از جمله:
- پردازش فایلهای بزرگ: ژنراتورها میتوانند برای خواندن و پردازش فایلهای بزرگ به صورت تکهای استفاده شوند، که از بارگذاری کل فایل در حافظه جلوگیری میکند.
- ایجاد توالیهای بینهایت: ژنراتورها میتوانند برای ایجاد توالیهای بینهایت، مانند توالی اعداد فیبوناچی، استفاده شوند.
- پایپلاینهای داده: ژنراتورها میتوانند برای ایجاد پایپلاینهای داده استفاده شوند، که در آن دادهها از طریق یک سری از مراحل پردازش عبور میکنند.
- همزمانی (Concurrency): ژنراتورها میتوانند برای پیادهسازی همزمانی با استفاده از coroutineها استفاده شوند.
ارسال مقدار به ژنراتور (Sending Values to a Generator)
علاوه بر دریافت مقادیر از یک ژنراتور، میتوانید با استفاده از متد send() مقادیری را به یک ژنراتور ارسال کنید. این امکان را فراهم میکند که ژنراتور را از خارج کنترل کنید و مقادیر را به آن ارسال کنید تا رفتار آن را تغییر دهید.
مثال
def echo_generator():
value = yield
while True:
value = yield value
gen = echo_generator()
next(gen) # شروع ژنراتور و دریافت اولین yield
print(gen.send("Hello")) # ارسال "Hello" به ژنراتور
print(gen.send("World")) # ارسال "World" به ژنراتور
در این مثال، ژنراتور echo_generator مقداری را که از طریق send() دریافت میکند، برمیگرداند. next(gen) برای شروع ژنراتور و دریافت اولین yield ضروری است. سپس، هر بار که send() فراخوانی میشود، مقدار ارسال شده به ژنراتور داده میشود و مقدار yield قبلی برگردانده میشود.
خاتمه
ژنراتورها و کلمه کلیدی yield ابزارهای قدرتمندی در پایتون هستند که به شما امکان میدهند توالیهای دادهای را به صورت تنبل ایجاد کنید. این ویژگی آنها را برای کار با مجموعههای دادهای بزرگ، توالیهای بینهایت و پیادهسازی الگوریتمهای پیچیده بسیار کارآمد میکند. با درک مفاهیم اساسی ژنراتورها و نحوه استفاده از yield، میتوانید کد پایتون خود را بهینهتر، خواناتر و کارآمدتر کنید.

بدون دیدگاه