تکرار گرها یا iterator ها در زبان های برنامه نویسی استفاده های زیادی دارند. برنامه نویسان در هنگام استفاده از ساختمان داده های آماده مکرراً از تکرارگرها استفاده میکنند. به عبارت ساده تر، با وجود iterator هاست که میتوانیم یک لیست، آرایه یا ساختمان داده خاص دیگری را پیمایش کرده و تک تک عناصر آنرا درون یک حلقه بررسی کنیم.
فهرست محتوای آموزش
تکرارگر یا iterator چیست ؟
شاید درک مفهوم تکرارگر یا iterator کمی سخت و گیج کننده باشد!
بگذارید با یک مثال بسیار ساده در زبان برنامه نویسی پایتون، مفهوم و موقعیت استفاده از تکرارگرها را ببینیم.
فرض کنید یک لیست از تعدادی عدد را در اختیار دارید و میخواهید روی این لیست پیمایش انجام دهید. همانطور که میدانید به سادگی، با استفاده از یک حلقه for این کار امکان پذیر است.
my_list = [1, 2, 3, 4] for x in my_list: print(x) #or do something ...
اما در پشت پرده اجرای حلقه فوق چه اتفاقی می افتد ؟
در حالت بسیار ساده، فرض کنید یک متغیر اشاره گر به اولین عنصر این لیست (در ساختار ساختمان داده لیست پایتون) داریم. وقتی لیست را درون یک حلقه for استفاده میکنیم، در اولین اجرای حلقه، اولین عنصر مورد بررسی و پیمایش قرار میگیرد.
در تکرار دوم حلقه، می بایست یک عنصر به جلو حرکت کنیم، یعنی اشاره گر ما باید به عنصر دوم (عنصر بعد از عنصر فعلی) اشاره کند.
فرآیند گفته شده به تعداد عناصر موجود در لیست تکرار شده تا دیگر عنصری برای جایگزینی با عنصر قبلی نداشته باشیم؛ در این صورت حلقه ما به اتمام می رسد. به همین سادگی!
مفهوم تکرارگر (iterator) در یک شئ به همین سادگی است که گفته شد!!
در تصویر زیر فرآیند کلی و ترتیبی تکرارگرها ترسیم شده است.
هنگامی که در حال ایجاد یک ساختمان داده دلخواه در زبان python هستیم، در صورتی که بخواهیم ساختمان داده ما ویژگی iterate شدن را داشته باشند، می بایست دو متد مرتبط با تکرارگرها را در کلاس خود پیاده سازی کنیم.
تابع __iter__
این تابع هنگامی صدا زده میشود که یک شئ از ساختمان داده ما در محلی برای پیمایش قرار گرفته باشد. (مثلا در حلقه for)
متد __iter__
در حقیقت یک شئ را به ما بر میگرداند که دارای یک متد به نام __next__
است.
تابع __next__
در متد __next__
فرآیندی انجام میشود که اشاره گر پیمایش عناصر ما یک گام به جلو حرکت کرده، به عنصر بعدی اشاره کند و در نهایت عنصر فعلی را به عنوان خروجی بدهد.
یک مثال برای درک ساختار iterator ها در پایتون
برای درک بهتر ساختار iterator ها، فرض کنید یک کلاس با یک متغیر به اسم current
داریم و مقدار اولیه آن را برابر با 1 می گذاریم.
class Numbers: def __init__(self): self.current = 1
حال اگر از کلاس آزمایشی خود یک نمونه ایجاد کرده و بخواهیم در یک حلقه ساده for استفاده کنیم، با خطا مواجه خواهیم شد!
obj = Numbers() for x in obj: print(x) #Run Result: #Traceback (most recent call last): #File "/home/SabzElco/Files/py/test.py", line 6, in <module> # for x in obj: #TypeError: 'Numbers' object is not iterable
همانطور که از آخرین خط ارور داده شده مشخص است، شئ های کلاس Numbers
قابلیت iterate شدن را ندارند یا به عبارتی iterable نیستند.
همانطور که پیش تر نیز گفته شد، برای iterable کردن کلاس مورد نظر، نیاز به دو تابع کمکی دیگر داریم. در ادامه دو متد لازم را به کلاس خود اضافه میکنیم. یعنی کلاس ما چیزی شبیه کد زیر خواهد شد.
class Numbers: def __init__(self): self.current = 1 def __iter__(self): return self def __next__(self): #have infinit end! output = self.current self.current += 1 return output
اکنون اگر همان حلقه قبلی را اجرا کنیم، در ابتدا عدد 1 را به صورت چاپ شده در خروجی میبینیم و در خط بعدی عدد 2، و این فرآیند تا بینهایت ادامه پیدا میکند.
معمولاً در ساختمان داده هایی که داریم، یک محدودیت خاصی وجود دارد؛ برای مثال، تعداد قابل شمارشی از عناصر در آن قرار داشته و یا در همین مثال آموزشی ما (کلاس Numbers) میتوان در نظر گرفت که پیمایش تا سقف خاصی پیشروی کند.
برای اینکه سقف مورد انتظار را به عنوان ورودی بگیریم، سازنده (constructor) کلاس را تغییر میدهیم و دو مقدار low و high را به عنوان ورودی در هنگام ایجاد شئ خواهیم گرفت.
همچنین برای کنترل عدم سرریز شدن از سقف مورد انتظار، می بایست شرطی جهت چک کردن رسیدن به حد مورد انتظار در متد __next__
قرار داد. در صورتی که به سقف خود رسیدیم، با استفاده از عبارت StopIteration
میتوانیم به حلقه for اعلام کنیم که پیمایش به پایان رسیده است.
class Numbers: def __init__(self, low, high): self.current = low self.limit = high def __iter__(self): return self def __next__(self): if self.current > self.limit: raise StopIteration output = self.current self.current += 1 return output
در نتیجه ی اجرای قطعه کد تست زیر، اعداد 1 تا 10 را در خروجی خواهیم داشت.
obj = Numbers(1, 10) for x in obj: print(x)
حال اگر همین شئ obj را در حلقه ای دیگر استفاده کنیم، خواهید دید که هیچ نتیجه ای حاصل نخواهد شد!
دلیل این امر این است که متغیر مربوط به current با یکبار اجرای عملیات پیمایش، به حداکثر خود رسیده و در دفعات دیگر پیمایش، به دلیل اینکه به limit رسیده، با اعلام StopIteration
حلقه را در ابتدای اجرا خاتمه میدهد.
برای جلوگیری از این کار، میتوان در متد __iter__
متغیرهایی که به عنوان اشاره گر (پیمایش گر) استفاده کرده ایم را به مقدار اولیه آنها بازگردانیم.
در اینجا صرفاً مقدار متغیر current تغییر میکند و مشکل ساز ما دقیقا همین متغیر است.
با نگهداری مقدار low در هنگام ساخت شئ از کلاس و انتساب آن در هنگام اجرای متد __iter__
به متغیر current میتوان مشکل را به راحتی حل کرد.
class Numbers: def __init__(self, low, high): self.low = low self.limit = high def __iter__(self): self.current = self.low return self def __next__(self): if self.current > self.limit: raise StopIteration output = self.current self.current += 1 return output
بعضاً به دلیل پیچیده بودن پروسه نگهداری عنصر فعلی و پرش به عنصر بعدی آن در هر گامِ پیمایش، کلاس iterable
را به عنوان یک کلاس جداگانه تعریف میکنند و در کلاس اصلی صرفا متد __iter__
را مورد استفاده قرار میدهند.
در صورت صدا زده شدن __iter__
، یک شئ از کلاس کمکی ساخته شده و بازگردانده میشود؛ لازم به ذکر است که کلاس کمکی حتما باید دو تابع __iter__
و __next__
را دارا باشد.
در مثال آموزشی ما، خود کلاس اصلی را iterable کردیم و به همین دلیل در متد __iter__
خود شئ را به عنوان یک شئی iterable بازمیگردانیم.
این آموزش برای همیشه رایگانه! میتونید با اشتراکگذاری لینک این صفحه از ما حمایت کنید یا با خرید یه فنجون نوشیدنی بهمون انرژی بدید!
میخوام یه نوشیدنی مهمونتون کنم
[…] کد زیر در هر بار اجرای حلقه تکرارگر، به صورت تصادفی از 1 تا 2 دقیقه استراحت […]