Generator در برنامه نویسی پایتون

4 سال پیش

Generator در برنامه نویسی پایتون

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

پیشنهاد ویژه : پکیج آموزش طراحی وب سایت با پایتون و جنگو

Generator در برنامه نویسی پایتون

در ساخت یک تکرار کننده در پایتون کار زیادی باید انجام شود. ما باید یک کلاس با روش ()__iter __  و ()__next __  پیاده سازی کنیم، وضعیت های داخلی را ردیابی کنیم و در صورت عدم بازگشت مقادیر StopIteration را به وجود آوریم.

این فرایند هم طولانی است و هم خسته کننده. Generator در چنین شرایطی به کمک ما می آید.

ژنراتورهای پایتون یک روش ساده برای ایجاد تکرار کننده ها هستند. تمام کارهایی که در بالا ذکر کردیم به طور خودکار توسط Generator در برنامه نویسی پایتون انجام می شوند.

به زبان ساده، ژنراتور تابعی است که یک شی (تکرار کننده) را برمی گرداند که می توانیم آن را تکرار کنیم.

 

ایجاد Generator در پایتون

ایجاد ژنراتور در پایتون نسبتاً ساده است. به آسانی تعریف یک تابع طبیعی است ، اما به جای دستور بازگشت (return)، با یک دستور بازده (yield) وجود دارد.

اگر یک تابع حداقل یک دستور yield داشته باشد (ممکن است حاوی دستورهای yield یا yield دیگری باشد)، به یک تابع ژنراتور تبدیل می شود. yield و return هم مقداری از یک تابع برمی گردانند.

تفاوت در این است که در حالی که یک دستور Return یک تابع را به طور کامل خاتمه می دهد، دستور yield تابع را متوقف می کند و تمام حالت های آن را ذخیره می کند، تا در آینده از همانجایی که متوقف شده بود به کار خود ادامه دهد.

تفاوت بین تابع Generator و تابع معمولی

در اینجا تفاوت یک تابع Generator و تابع معمولی آورده شده است.

  • تابع ژنراتور شامل یک یا چند دستور yield است.
  • هنگامی که فراخوانی می شود، آن یک شی object (تکرار کننده) را برمی گرداند اما بلافاصله اجرا نمی شود.
  • متدهایی مانند ()__iter __ و ()__next __  به طور خودکار اجرا می شوند. بنابراین می توانیم با استفاده از ()next  از طریق آیتم ها تکرار کنیم.
  • پس از تابع yield، تابع متوقف شده و کنترل به تماس فراخوانی های بعد منتقل می شود.
  • متغیرهای محلی و حالتهای آنها بین فراخوانی های پی در پی به خاطر سپرده می شوند.
  • سرانجام، هنگامی که تابع خاتمه می یابد، StopIteration به طور خودکار در فراخوانی های بعدی افزایش می یابد.

در اینجا مثالی برای نشان دادن همه نکاتی که در بالا گفته شد آورده شده است. ما یک تابع ژنراتور به نام ()my_gen با چندین دستور yield داریم.

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

 

یک اجرای تعاملی در مفسر در زیر آورده شده است. اینها را در پوسته پایتون اجرا کنید تا خروجی را ببینید.

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

یک اجرای تعاملی در مفسر در زیر آورده شده است. اینها را در پوسته پایتون اجرا کنید تا خروجی را ببینید.

>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()

>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
۱
>>> # Once the function yields, the function is paused and the control is transferred to the caller.

>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
۲

>>> next(a)
This is printed at last
۳

>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

 

یک نکته جالب توجه در مثال بالا این است که مقدار متغیر n بین هر فراخوانی به خاطر سپرده می شود.

برخلاف توابع نرمال، متغیرهای محلی هنگام yield تابع از بین نمی روند. بعلاوه، شی ژنراتور فقط یکبار قابل تکرار است.

برای راه اندازی مجدد فرآیند، ما باید یک شی Generator دیگر با استفاده از چیزی مانند ()a = my_gen  ایجاد کنیم.

آخرین نکته ای که باید به آن توجه کنیم این است که می توانیم مستقیماً از Generator دارای حلقه برای استفاده کنیم.

به این دلیل که حلقه for یک تکرار کننده را می گیرد و با استفاده از تابع ()next  آن را تکرار می کند. با افزایش StopIteration به طور خودکار پایان می یابد.

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n


# Using for loop
for item in my_gen():
    print(item)

 

خروجی

This is printed first
۱
This is printed second
۲
This is printed at last
۳

 

Generator پایتون با حلقه

مثال بالا کاربرد کمتری دارد و ما فقط برای اینکه تصور کنیم چه اتفاقی در بک گراند می افتد از آن استفاده کردیم.

به طور معمول، توابع ژنراتور با یک حلقه با شرایط خاتمه مناسب اجرا می شوند.

بیایید مثالی از ژنراتور را بگیریم که یک رشته را برعکس می کند.

def rev_str(my_str):
    length = len(my_str)
    for i in range(length - 1, -1, -1):
        yield my_str[i]


# For loop to reverse the string
for char in rev_str("hello"):
    print(char)

خروجی

o
l
l
e
h

 

در این مثال، از تابع ()range استفاده کرده ایم تا اندیس را به ترتیب معکوس با استفاده از حلقه for بدست آوریم.

توجه: این تابع ژنراتور نه تنها با رشته ها، بلکه با انواع تکرار شونده دیگر مانند لیست ، تاپل و غیره نیز کار می کند.

 

عبارت های Generator پایتون

ژنراتورهای ساده را می توان با استفاده از عبارات Generator به راحتی در پرواز ایجاد کرد. که ساخت ژنراتور ها را آسان می کند.

مشابه توابع lambda که توابع ناشناس را ایجاد می کنند، عبارات Generator توابع مولد ناشناس را ایجاد می کنند.

نحوعبارت ژنراتور مشابه comprehension لیست در پایتون می باشد. اما براکت ها جایگزین پرانتز می شوند.

تفاوت عمده بین comprehension لیست و عبارت ژنراتور این است که یک comprehension لیست، لیست را تولید می کند در حالی که عبارت Generator یک آیتم را در یک زمان تولید می کند.

generatorها lazy (کندرو) هستند (تولید آیتم ها فقط در صورت درخواست). به همین دلیل، یک عبارت ژنراتور نسبت به comprehension لیست از لحاظ حافظه بسیار کارآمدتر است.

# Initialize the list
my_list = [1, 3, 6, 10]

# square each term using list comprehension
list_ = [x**2 for x in my_list]

# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)

print(list_)
print(generator)

 

خروجی

[۱, ۹, ۳۶, ۱۰۰]
<generator object <genexpr> at 0x7f5d4eb4bf50>

 

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

در اینجا نحوه دستیابی به موارد از عبارت Generator آورده شده است:

# Initialize the list
my_list = [1, 3, 6, 10]

a = (x**2 for x in my_list)
print(next(a))

print(next(a))

print(next(a))

print(next(a))

next(a)

 

خروجی

۱
۹
۳۶
۱۰۰
Traceback (most recent call last):
  File "<string>", line 15, in <module>
StopIteration

 

عبارت Generator را می توان به عنوان آرگومان تابع استفاده کرد.

>>> sum(x**2 for x in my_list)
۱۴۶

>>> max(x**2 for x in my_list)
۱۰۰

 

کاربردهای Generator در برنامه نویسی پایتون

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

۱٫ اجرای آسان

Generator را می توان به روش واضح و مختصری در مقایسه با نمونه کلاس تکرار کننده آنها پیاده سازی کرد. در زیر مثالی برای پیاده سازی توالی توان ۲ با استفاده از کلاس تکرار آورده شده است.

class PowTwo:
    def __init__(self, max=0):
        self.n = 0
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > self.max:
            raise StopIteration

        result = 2 ** self.n
        self.n += 1
        return result

 

برنامه فوق طولانی و گیج کننده بود. حال اجازه دهید همان کار را با استفاده از یک تابع ژنراتور انجام دهیم.

def PowTwoGen(max=0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

 

از آنجا که ژنراتورها به طور خودکار جزئیات را ردیابی می کنند، اجرای این کد مختصر و تمیزتر بود.

۲٫ حافظه کارآمد

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

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

۳٫ جریان بی نهایت 

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

تابع ژنراتور زیر می تواند تمام اعداد زوج را تولید کند (حداقل از لحاظ تئوری).

def all_even():
    n = 0
    while True:
        yield n
        n += 2

 

۴٫ ژنراتورهای خط لوله

از چندین ژنراتور می توان برای خط لوله مجموعه ای از عملیات استفاده کرد. این بهتر است با استفاده از یک مثال نشان داده شود.

فرض کنید ژنراتوری داریم که اعداد سری فیبوناچی را تولید می کند. و ما یک ژنراتور دیگر برای مجذور اعداد داریم.

اگر بخواهیم از مجموع مربعات اعداد در سری فیبوناچی مطلع شویم، می توانیم این کار را به روش زیر با خط لوله کردن خروجی توابع ژنراتور با هم انجام دهیم.

def fibonacci_numbers(nums):
    x, y = 0, 1
    for _ in range(nums):
        x, y = y, x+y
        yield x

def square(nums):
    for num in nums:
        yield num**2

print(sum(square(fibonacci_numbers(10))))

 

خروجی

۴۸۹۵

 

منبع.

 

لیست جلسات قبل آموزش برنامه نویسی پایتون

  1. آموزش نصب و اجرای برنامه نویسی پایتون
  2. کلیدواژه ها و شناسه های برنامه نویسی پایتون
  3. دستورات، تورفتگی ها و کامنت ها در برنامه نویسی پایتون
  4. متغیرها، ثابت ها و لیترال ها در برنامه نویسی پایتون 
  5. انواع داده ها در برنامه نویسی پایتون
  6. تبدیل نوع در برنامه نویسی پایتون
  7. ورودی، خروجی و وارد کردن در برنامه نویسی پایتون
  8. عملگرها در برنامه نویسی پایتون
  9. نام و دامنه در برنامه نویسی پایتون
  10. دستور شرطی if…else در برنامه نویسی پایتون
  11. حلقه for در برنامه نویسی پایتون
  12. حلقه while در برنامه نویسی پایتون
  13. دستورات break و continue در برنامه نویسی پایتون
  14. دستور pass در برنامه نویسی پایتون
  15. توابع در برنامه نویسی پایتون
  16. آرگومان تابع در برنامه نویسی پایتون
  17. تابع بازگشتی در برنامه نویسی پایتون
  18. تابع بی نام/ تابع لامبدا در برنامه نویسی پایتون
  19. متغیرهای سراسری، محلی و غیر محلی در برنامه نویسی پایتون
  20. کلیدواژه global در برنامه نویسی پایتون
  21. ماژول های برنامه نویسی پایتون
  22. پکیج ها در برنامه نویسی پایتون
  23. اعداد و تبدیل نوع داده در برنامه نویسی پایتون 
  24. لیست در برنامه نویسی پایتون
  25. تاپل در برنامه نویسی پایتون 
  26. رشته ها در برنامه نویسی پایتون
  27. مجموعه ها در برنامه نویسی پایتون
  28. دیکشنری در برنامه نویسی پایتون
  29. عملیات ورودی/خروجی در برنامه نویسی پایتون 
  30. دایرکتوری و مدیریت فایل ها در برنامه نویسی پایتون
  31. خطاها و استثناهای توکار در برنامه نویسی پایتون
  32. مدیریت استثناها در برنامه نویسی پایتون
  33. استثناهای تعریف شده توسط کاربر در برنامه نویسی پایتون
  34. برنامه ‌نویسی شی گرا در پایتون
  35. اشیا و کلاس ها در برنامه نویسی پایتون
  36. وراثت در برنامه نویسی پایتون
  37. وراثت چندگانه در برنامه نویسی پایتون
  38. سربارگذاری عملگرها در برنامه نویسی پایتون
  39. تکرار کننده ها در برنامه نویسی پایتون

 

 

0
برچسب ها :
نویسنده مطلب erfan molaei

دیدگاه شما

بدون دیدگاه