Anophel-آنوفل استفاده از Generator ها و Yield در پایتون

استفاده از Generator ها و Yield در پایتون

انتشار:
2

آیا تا به حال مجبور شده اید با مجموعه داده ای آنقدر بزرگ کار کنید که مموری سیستم شما را تحت تأثیر قرار دهد؟ یا شاید شما یک تابع مختلط دارید که هر بار که فراخوانی می شود نیاز به حفظ یک حالت داخلی دارد، اما این تابع برای توجیه ایجاد کلاس خود بسیار کوچک است. در این موارد و خیلی از موارد مشابه دیگر Generator ها و Yield در پایتون می توانند در این مورد بسیار مفید باشد.


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

استفاده از Generator ها
توابع generator که با PEP 255 معرفی شدند، نوع خاصی از تابع هستند که یک lazy iterator را برمی گرداند. lazy iterator آبجکتی است که می توانید مانند یک لیست روی آنها حلقه بزنید. با این حال، بر خلاف لیست ها، lazy iterator ها مقادیر خود را در مموری ذخیره نمی کنند.

حالا که ایده ای تقریبی از کاری که یک generator انجام می دهد دارید، ممکن است برایتان سوال باشد که در عمل چگونه به نظر می رسند. بیایید به دو مثال نگاه کنیم و آن ها را باهم بررسی کنیم. در ابتدا نحوه عملکرد generator ها را خواهید دید.

مثال 1: خواندن فایل های بزرگ
یک مورد معمول استفاده از generator ها کار با جریان داده یا فایل های بزرگ مانند فایل های CSV است. این فایل های متنی داده ها را با استفاده از کاما به ستون ها جدا می کنند. این فرمت یک روش رایج برای اشتراک گذاری داده ها است. حال، اگر بخواهید تعداد ردیف های یک فایل CSV را بشمارید، چه؟ بلوک کد زیر یک راه برای شمارش آن ردیف ها را نشان می دهد:

csv_gen = csv_reader("some_csv.txt")
row_count = 0

for row in csv_gen:
    row_count += 1

print(f"Row count is {row_count}")

با نگاهی به این مثال، ممکن است انتظار داشته باشید که csv_gen یک لیست باشد. برای پر کردن این لیست، ()csv_reader یک فایل را باز می کند و مقادیر آن را در csv_gen بارگذاری می کند. سپس، برنامه روی لیست تکرار می شود و row_count را برای هر ردیف افزایش می دهد.

این یک توضیح منطقی است، اما اگر فایل بسیار بزرگ باشد، آیا کدهای بالا همچنان کار می کند؟ اگر فایل از حافظه ای که در دسترس دارید بزرگتر باشد چه؟ برای پاسخ به این سوال، فرض کنید ()csv_reader فقط فایل را باز می کند و آن را در یک آرایه می خواند:

def csv_reader(file_name):
    file = open(file_name)
    result = file.read().split("\n")
    return result

این تابع یک فایل مشخص را باز می کند و از ()file.read همراه با ()split. برای اضافه کردن هر خط به عنوان یک آیتم جداگانه به یک لیست استفاده می کند. اگر بخواهید از این ()csv_reader در کد شمارش ردیفی که در بالا دیدید استفاده کنید، خروجی زیر را دریافت خواهید کرد:

Traceback (most recent call last):
  File "ex1_naive.py", line 22, in <module>
    main()
  File "ex1_naive.py", line 13, in main
    csv_gen = csv_reader("file.txt")
  File "ex1_naive.py", line 6, in csv_reader
    result = file.read().split("\n")
MemoryError

در این حالت، open() یک آبجکت generator را برمی‌گرداند که می‌توانید با lazily iterate آن را خط به خط تکرار کنید. با این حال، ()file.read().split همه چیز را به یکباره در حافظه بارگذاری می کند و باعث ایجاد MemoryError می شود.

قبل از اینکه بخواهد این اتفاق بیفتد، احتمالاً متوجه خواهید شد که سیستم شما به سرعت در حال خزیدن است. حتی ممکن است لازم باشد برنامه را با KeyboardInterrupt ببندید. بنابراین، چگونه می توانید این فایل های داده عظیم را مدیریت کنید؟ بیاید نگاهی به تعریف جدیدی از ()csv_reader داشته باشیم:

def csv_reader(file_name):
    for row in open(file_name, "r"):
        yield row

در این کدها، فایل را باز می‌کنید، آن را تکرار می‌کنید و یک ردیف ایجاد می‌کنید. این کد باید خروجی زیر را بدون خطای MemoryError  تولید کند:

Row count is 6959985394

خب، شما اساساً ()csv_reader را به یک تابع generator تبدیل کرده اید. این کد یک فایل را باز می کند، در هر خط حلقه می زند و به جای بازگرداندن هر سطر، آن را نشان می دهد.

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

csv_gen = (row for row in open(file_name))

این یک راه مختصرتر برای ایجاد لیست csv_gen است. در انتهای این مقاله نیز در مورد Yield بیشتر خواهید آموخت.فقط این تفاوت کلیدی را به خاطر بسپارید:

  1. استفاده از yield منجر به یک آبجکت generator می شود.
  2. استفاده از return تنها در خط اول فایل ایجاد می شود.

 

 

مثال 2: ایجاد یک دنباله بی نهایت
بیایید یک مثال خیلی متفاوت تر بزنیم و به تولید یک دنباله بی نهایت نگاه کنیم. در پایتون، برای به دست آوردن یک دنباله محدود، ()range را فراخوانی می کنید:

>>> a = range(5)
>>> list(a)
[0, 1, 2, 3, 4]

با این حال، تولید یک دنباله بی نهایت نیاز به استفاده از یک generator دارد، زیرا حافظه سیستم شما محدود است:

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

این بلوک کد کوتاه و بسبار زیبا است. ابتدا متغیر num را مقداردهی اولیه کرده و یک حلقه بی نهایت راه اندازی می کنید. سپس، بلافاصله num را به دست می آورید تا بتوانید حالت اولیه را بگیرید. این کار ()rangeرا تقلید می کند.

پس از Yield هم  num به 1 افزایش می دهید. اگر این را با یک حلقه for امتحان کنید، مثل زیر بی نهایت خواهد شد:

>>> for i in infinite_sequence():
...     print(i, end=" ")
...
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
[...]
6157818 6157819 6157820 6157821 6157822 6157823 6157824 6157825 6157826 6157827
6157828 6157829 6157830 6157831 6157832 6157833 6157834 6157835 6157836 6157837
6157838 6157839 6157840 6157841 6157842
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

این برنامه تا زمانی که آن را به صورت دستی متوقف نکنید به اجرای آن ادامه خواهد داد.

به جای استفاده از حلقه for، می‌توانید مستقیماً ()next را روی آبجکت generator فراخوانی کنید. این برای آزمایش یک generator در کنسول مفید است:

>>> gen = infinite_sequence()
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
3

در اینجا، شما یک generator به نام gen دارید که به صورت دستی با فراخوانی مکرر ()next آن را تکرار می کنید. این کار باعث می شود تا مطمئن شوید که generator های شما خروجی مورد انتظار شما را تولید می کنند.

دقت کنید که وقتی از ()next استفاده می کنید، پایتون ()__next__. را روی تابعی که به عنوان پارامتر ارسال می کنید، فراخوانی می کند. یکسری چیزا وجود دارد که این پارامترسازی را اجازه می دهد، اما از محدوده این مقاله فراتر می رود. با تغییر پارامتری که به ()next منتقل می کنید آزمایش کنید و ببینید چه اتفاقی می افتد!

مثال 3: تشخیص پالیندروم
شما می توانید از دنباله های بی نهایت به شکل های مختلف استفاده کنید، اما یکی از کاربردهای عملی آنها در ساخت آشکارسازهای پالیندروم است. یک آشکارساز پالیندروم تمام دنباله‌های حروف یا اعداد را که پالیندروم هستند پیدا می‌کند. اینها کلمات یا اعدادی هستند که به صورت یکسان از هر دور طرف خوانده می شوند، مانند 121. ابتدا پالیندروم عددی خود را تعریف کنید:

def is_palindrome(num):
    # Skip single-digit inputs
    if num // 10 == 0:
        return False
    temp = num
    reversed_num = 0

    while temp != 0:
        reversed_num = (reversed_num * 10) + (temp % 10)
        temp = temp // 10

    if num == reversed_num:
        return num
    else:
        return False

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

>>> for i in infinite_sequence():
...     pal = is_palindrome(i)
...     if pal:
...         print(i)
...
11
22
33
[...]
99799
99899
99999
100001
101101
102201
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 5, in is_palindrome

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

در عمل، بعید است که generator دنباله نامتناهی خود را بنویسید. ماژول itertools یک generator دنباله بینهایت بسیار کارآمد را با ()itertools.count فراهم می کند.

اکنون که یک مورد استفاده ساده برای یک generator دنباله نامتناهی دیدید، بیایید عمیق‌تر به نحوه عملکرد generator ها بپردازیم.

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

توابع generator درست مانند توابع معمولی به نظر می رسند و عمل می کنند، اما با یک مشخصه تعیین کننده. توابع generator به جای return از کلمه کلیدی Yield پایتون استفاده می کنند. تابع generator ی را که قبلا نوشتید به یاد بیاورید:

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

این به نظر یک تعریف تابع معمولی است، به جز yield و کدی که به دنبال آن است. yield نشان می دهد که یک مقدار را صدا میزند و به کجا ارسال می شود، اما بر خلاف return ، بعد از آن از تابع خارج نمی شوید.

در عوض، وضعیت تابع به خاطر سپرده می شود. به این ترتیب، هنگامی که ()next روی یک آبجکت generator فراخوانی می شود (در یک حلقه for)، متغیر num قبلی افزایش می یابد، و سپس دوباره به دست می آید. از آنجایی که توابع generator شبیه توابع دیگر هستند و بسیار شبیه به آنها عمل می کنند، می توانید فرض کنید که کلمه کلیدی generator بسیار شبیه سایر comprehensions دیگر موجود در پایتون هستند.

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

 

این مثال از به توان 2 رساندن برخی اعداد را در نظر بگیرید:

>>> nums_squared_lc = [num**2 for num in range(5)]
>>> nums_squared_gc = (num**2 for num in range(5))

هر دو nums_squared_lc و nums_squared_gc اساساً یکسان به نظر می رسند، اما یک تفاوت اساسی وجود دارد. آیا می توانید آن را تشخیص دهید؟ وقتی هر یک از این آبجکت ها را بررسی می کنید، به اتفاقاتی که می افتد دقت کنید:

>>> nums_squared_lc
[0, 1, 4, 9, 16]
>>> nums_squared_gc
<generator object <genexpr> at 0x107fbbc78>

آبجکت اول از براکت برای ساختن یک لیست استفاده کرد، در حالی که آبجکت دوم با استفاده از پرانتز یک generator  ایجاد کرد. خروجی تأیید می کند که یک آبجکت generator ایجاد کرده اید و از یک لیست متمایز است.

مشخصات عملکردی generator ها
قبلاً یاد گرفتید که generator ها یک راه عالی برای بهینه سازی حافظه هستند. در حالی که یک generator دنباله نامتناهی یک مثال افراطی از این بهینه‌سازی است، بیایید نمونه‌های مربع‌سازی اعدادی را که به‌تازگی دیده‌اید تقویت کنیم و اندازه آبجکت های حاصل را بررسی کنیم. می توانید این کار را با یک فراخوانی به ()sys.getsizeof:

>>> import sys
>>> nums_squared_lc = [i ** 2 for i in range(10000)]
>>> sys.getsizeof(nums_squared_lc)
87624
>>> nums_squared_gc = (i ** 2 for i in range(10000))
>>> print(sys.getsizeof(nums_squared_gc))
120

در این مورد، لیستی که از لیست comprehensions دریافت می کنید 87624 بایت است، در حالی که آبجکت generator ها تنها 120 بایت است. این به این معنی است که لیست بیش از 700 برابر بزرگتر از آبجکت generator است!

با این حال یک چیز را باید در نظر داشت. اگر لیست کوچکتر از حافظه در دسترس ماشین در حال اجرا باشد، در این صورت لیست comprehensions می تواند سریعتر از generator   معادل ارزیابی شود. برای درک این موضوع، بیایید نتایج حاصل از دو comprehensions بالا را جمع بندی کنیم. می‌توانید با ()cProfile.run:

>>> import cProfile
>>> cProfile.run('sum([i * 2 for i in range(10000)])')
         5 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <string>:1(<listcomp>)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.001    0.001 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


>>> cProfile.run('sum((i * 2 for i in range(10000)))')
         10005 function calls in 0.003 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10001    0.002    0.000    0.002    0.000 <string>:1(<genexpr>)
        1    0.000    0.000    0.003    0.003 <string>:1(<module>)
        1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
        1    0.001    0.001    0.003    0.003 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

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

این اندازه گیری ها فقط برای آبجکت هایی که با کلمه کلیدی generator ساخته شده اند معتبر نیستند. آنها همچنین برای آبجکت های ساخته شده از تابع generator مشابه یکسان هستند زیرا generator های حاصل معادل هستند.

به یاد داشته باشید،لیست comprehensions ، لیست های کامل را برمی گرداند، در حالی که کلمه کلیدی generator،generator ها را برمی گرداند. generator ها چه از یک تابع یا یک عبارت ساخته شده باشند یکسان کار می کنند. استفاده از یک عبارت فقط به شما امکان می دهد generator های ساده را در یک خط مشخص کنید، با yield فرضی در پایان هر تکرار داخلی.

دستور yield مطمئناً پایه‌ای است که تمام عملکرد generator ها بر آن استوار است، بنابراین بیایید به نحوه عملکرد yield در پایتون بپردازیم.

درک Yield پایتون
در کل، yield یک کلمه کلیدی نسبتاً ساده است. وظیفه اصلی آن کنترل جریان یک تابع generator به روشی شبیه به دستورات بازگشتی است. همانطور که در بالا به طور خلاصه ذکر شد، کلمه کلیدی yield پایتون چند ترفند در آستین خود دارد.

هنگامی که یک تابع generator را فراخوانی می کنید یا از یک generator استفاده می کنید، یک iterator خاص به نام generator را برمی گردانید. شما می توانید این generator ها را به یک متغیر اختصاص دهید تا از آن استفاده کنید. وقتی متدهای خاصی را روی generator فراخوانی می کنید، مانند ()next، کد داخل تابع تا yield اجرا می شود.

وقتی دستور yield پایتون زده می‌شود، برنامه اجرای تابع را به حالت تعلیق در می‌آورد و مقدار yield را برمی‌گرداند. (در مقابل، return اجرای تابع را به طور کامل متوقف می کند.) هنگامی که یک تابع به حالت تعلیق در می آید، وضعیت آن تابع ذخیره می شود. این شامل هر گونه اتصال متغیر محلی به generator ها، اشاره گر دستورالعمل، internal stack، و هرگونه exception handling می شود.

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

>>> def multi_yield():
...     yield_str = "This will print the first string"
...     yield yield_str
...     yield_str = "This will print the second string"
...     yield yield_str
...
>>> multi_obj = multi_yield()
>>> print(next(multi_obj))
This will print the first string
>>> print(next(multi_obj))
This will print the second string
>>> print(next(multi_obj))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

نگاهی دقیق تر به آخرین فراخوانی ()next بیندازید. می توانید ببینید که اجرا با یک traceback شده است. این به این دلیل است که generator ها، مانند همه iterator ها، می توانند خسته شوند. مگر اینکه generator شما بی نهایت باشد، می توانید فقط یک بار از طریق آن تکرار کنید. هنگامی که همه مقادیر ارزیابی شدند، تکرار متوقف می شود و حلقه for خارج می شود. اگر از ()next استفاده کردید، در عوض یک استثنا StopIteration دریافت خواهید کرد.

yield را می توان به روش های بسیاری برای کنترل جریان اجرای generator استفاده کرد. تا آنجایی که خلاقیت شما اجازه می دهد، می توان از چندین کلمه کلیدی yield پایتون استفاده کرد.

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

  • ()send.
  • ()throw.
  • ()close.

نحوه استفاده از ()send.
برای این بخش می خواهید برنامه ای بسازید که از هر سه روش استفاده می کند. این برنامه مانند قبل، اما با چند ترفند، پالیندروم های عددی را چاپ می کند. پس از مواجهه با یک palindrome، برنامه جدید شما یک رقم اضافه می کند و از آنجا شروع به جستجو برای عدد بعدی می کند. شما همچنین استثناها را با ()throw. کنترل می کنید و generator ها را پس از مقدار معینی از ارقام با ()close. متوقف می کنید. ابتدا، اجازه دهید کد پالیندروم شما را به یاد بیاوریم:

def is_palindrome(num):
    # Skip single-digit inputs
    if num // 10 == 0:
        return False
    temp = num
    reversed_num = 0

    while temp != 0:
        reversed_num = (reversed_num * 10) + (temp % 10)
        temp = temp // 10

    if num == reversed_num:
        return True
    else:
        return False

این همان کدی است که قبلاً دیدید، با این تفاوت که اکنون برنامه کاملاً True یا False برمی‌گرداند. همچنین باید generator دنباله نامتناهی اصلی خود را تغییر دهید، مانند:

def infinite_palindromes():
    num = 0
    while True:
        if is_palindrome(num):
            i = (yield num)
            if i is not None:
                num = i
        num += 1

در اینجا تغییرات زیادی وجود دارد! اولین موردی که می بینید در خط 5 است، جایی که i=(yield num). اگرچه قبلاً یاد گرفتید که yield یک کلمه کلیدی است، اما این تمام داستان نیست.

از پایتون 2.5 yield به جای یک expression باشد ، یک statement است. البته هنوز هم می توانید از آن به عنوان statement استفاده کنید. اما اکنون، شما همچنین می توانید آن را همانطور که در بلوک کد بالا مشاهده می کنید، استفاده کنید، جایی که مقدار بدست آمده را می گیرد. این به شما امکان می دهد مقدار yield را دستکاری کنید. مهمتر از آن، به شما این امکان را می دهد که یک مقدار ()send. را به generator بازگردانید. هنگام اجرا  yield بالا می رود، مقدار ارسال شده را می گیرم.

همچنین بررسی می‌کنید که if i is not None، که اگر ()next روی آبجکت generator فراخوانی شود ممکن است اتفاق بیفتد. (این همچنین می تواند زمانی اتفاق بیفتد که با یک حلقه for تکرار می کنید.) اگر i یک مقدار داشته باشد، num را با مقدار جدید به روز می کنید. اما صرف نظر از اینکه i مقداری را نگه می دارد یا نه، سپس num را افزایش می دهید و حلقه را دوباره شروع می کنید.

حالا به کد تابع اصلی نگاهی بیندازید که کمترین عدد را با یک رقم دیگر به generator برمی‌گرداند. به عنوان مثال، اگر palindrome 121 باشد، .send() 1000 خواهد کرد:

pal_gen = infinite_palindromes()
for i in pal_gen:
    digits = len(str(i))
    pal_gen.send(10 ** (digits))

با این کد، آبجکت generator را ایجاد کرده و از طریق آن تکرار می کنید. این برنامه فقط زمانی یک مقدار را به دست می آورد که یک palindrome پیدا شود. از()len برای تعیین تعداد ارقام در آن پالیندروم استفاده می کند. سپس، 10 ** (digits) را به generator ارسال می کند. این اجرا را به منطق generator بازمی گرداند و 10 ** (digits) را به i اختصاص می دهد. از آنجایی که i اکنون یک مقدار دارد، برنامه num را به‌روزرسانی می‌کند، افزایش می‌دهد و دوباره وجود پالیندروم را بررسی می‌کند.

هنگامی که کد شما یک پالیندروم دیگر را پیدا کرد و به دست آورد، از طریق حلقه for تکرار خواهد کرد. این همان تکرار با ()next است. ژنراتور نیز در خط 5 با i = (yield num) انتخاب می کند. با این حال، اکنون i is None، زیرا شما به صراحت مقداری ارسال نکردید.

آنچه شما در اینجا ایجاد کرده اید یک coroutine یا یک تابع generator است که می توانید داده ها را به آن منتقل کنید. اینها برای constructing data pipelines مفید هستند، که در مقاله های بعدی خواهید دید، برای ساخت آنها ضروری نیستند.

اکنون که با send(). آشنا شدید، بیایید نگاهی بهthrow().بیندازیم.

نحوه استفاده از ()throw.
()throw. به شما اجازه می دهد تا استثنائات را با generator  بگیرید. در مثال زیر، شما استثنا را در خط 6 مطرح می کنید. این کد زمانی که ارقام به 5 رسید، یک ValueError ایجاد می کند:

pal_gen = infinite_palindromes()
for i in pal_gen:
    print(i)
    digits = len(str(i))
    if digits == 5:
        pal_gen.throw(ValueError("We don't like large palindromes"))
    pal_gen.send(10 ** (digits))

این همان کد قبلی است، اما اکنون بررسی خواهید کرد که آیا ارقام برابر با 5 است یا خیر. برای تأیید اینکه این کار همانطور که انتظار می رود کار می کند، به خروجی کد نگاهی بیندازید:

11
111
1111
10101
Traceback (most recent call last):
  File "advanced_gen.py", line 47, in <module>
    main()
  File "advanced_gen.py", line 41, in main
    pal_gen.throw(ValueError("We don't like large palindromes"))
  File "advanced_gen.py", line 26, in infinite_palindromes
    i = (yield num)
ValueError: We don't like large palindromes

()throw. در هر منطقه ای که ممکن است نیاز به گرفتن استثنا داشته باشید مفید است. در این مثال، از ()throw. برای کنترل زمان توقف تکرار از طریق generator استفاده کردید. شما می توانید این کار را با ()close. نیز انجام دهید.


نحوه استفاده از close().
همانطور که از نام آن پیداست، ()close. به شما اجازه می دهد تا یک generator را متوقف کنید. این می تواند به ویژه هنگام کنترل یک generator دنباله بی نهایت مفید باشد. بیایید کد بالا را با تغییر ()throw. به ()close. برای توقف تکرار به روز کنیم:

pal_gen = infinite_palindromes()
for i in pal_gen:
    print(i)
    digits = len(str(i))
    if digits == 5:
        pal_gen.close()
    pal_gen.send(10 ** (digits))

به جای فراخوانی ()throw.، از ()close. در خط 6 استفاده می کنید.


 مزیت استفاده از ()close. این است که StopIteration را افزایش می دهد، استثنایی که برای علامت دادن به پایان یک تکرار کننده محدود استفاده می شود:

11
111
1111
10101
Traceback (most recent call last):
  File "advanced_gen.py", line 46, in <module>
    main()
  File "advanced_gen.py", line 42, in main
    pal_gen.send(10 ** (digits))
StopIteration

اکنون که در مورد روش‌های ویژه‌ای که با generator ها ارائه می‌شوند، اطلاعات بیشتری کسب کرده‌اید.

نتجیه

ما در این مقاله به صورت کامل generator ها را بررسی کردیم و مثال هایی از آن نیز زدیم،امیدوارم نهایت استفاده را از این مقاله داشته باشید. اگر نظری یا موردی دوست داشتید می توانید در قسمت نظرات آن را با ما به اشتراک بگذارید.

#generator_python#python#پایتون#تکرار_پایتون
نظرات ارزشمند شما :

در حال دریافت...

مقاله های مشابه

در حال دریافت...