Iterator شیء ای است که تعداد قابل شمارشی از مقادیر را در خود جای داده است. در واقع ما می توانیم درون این شیء iterate کنیم، یعنی بین مقادیر مختلف حرکت/گردش کنیم. این مفهوم iterator است اما از نظر فنی iterator در پایتون شیء ای است که پروتکل iterator را پیاده سازی کند؛ یعنی متدهای ()__iter__
و ()__next__
در واقع در زبان پایتون list ها و tuple ها و dictionary ها و set ها همگی iterable (یعنی قابل iterate شدن) هستند. این ها نگهدارنده های iterable ای هستند که می توانید یک iterator از آن ها بگیرید. تمامی این اشیاء یک متد ()iter دارند که با استفاده از آن می توانیم یک iterator بگیریم:
mytuple = ("apple", "banana", "cherry") myit = iter(mytuple) print(next(myit)) print(next(myit)) print(next(myit))
خروجی:
apple
banana
cherry
در کد بالا ابتدا با استفاده از ()iter یک iterator ساخته و آن را درون متغیر myit قرار داده ایم. سپس با هر دستور next به مقدار بعدی منتقل شده و آن را چاپ می کنیم. شما می توانید این کار را حتی با رشته ها نیز انجام دهید:
mystr = "banana" myit = iter(mystr) print(next(myit)) print(next(myit)) print(next(myit)) print(next(myit)) print(next(myit)) print(next(myit))
خروجی:
b
a
n
a
n
a
البته شما مجبور به استفاده از این روش نیستید بلکه می توانید با همان روش قدیمی استفاده از حلقه ی for
کار خود را انجام دهید. به مثال زیر توجه کنید:
mytuple = ("apple", "banana", "cherry") for x in mytuple: print(x)
خروجی:
apple
banana
cherry
همچنین برای رشته ها می گوییم:
mystr = "banana" for x in mystr: print(x)
خروجی:
b
a
n
a
n
a
نحوه ی کار حلقه ی for در پشت صحنه این است که ابتدا یک شیء iterator ساخته و سپس به صورت خودکار متد ()next را صدا میزند، بدین ترتیب دیگر نیاز نیست که خودمان به صورت دستی آن را صدا بزنیم.
برای ساختن یک شیء یا کلاسِ iterator باید متدهای ()__iter__
و ()__next__
را در آن پیاده سازی کنید. همانطور که در جلسه ی شیء گرایی پایتون توضیح داده بودم، تمام کلاس ها به صورت پیش فرض دارای متدی به نام ()__init__
هستند که با استفاده از آن می توانید عملیات های ابتدایی (مانند مقدار دهی اولیه و ....) را انجام دهید. متد ()__iter__
نیز دقیقا مانند ()__init__
کار می کند یعنی می توانید در آن عملیات های خودتان را انجام دهید اما همیشه باید خودِ شیء iterator را برگردانید (دستور return
). متد ()__next__
نیز به ما اجاره ی اجرای عملیات ها را می دهد اما باید همیشه آیتم بعدی مجموعه را برگرداند.
برای مثال در کد زیر یک iterator ساخته ایم که اعداد را برمی گرداند؛ از یک شروع می شود و در هر گردش یک واحد به واحد قبلی اضافه می کند:
class MyNumbers: def __iter__(self): self.a = 1 return self def __next__(self): x = self.a self.a += 1 return x myclass = MyNumbers() myiter = iter(myclass) print(next(myiter)) print(next(myiter)) print(next(myiter)) print(next(myiter)) print(next(myiter))
خروجی:
1
2
3
4
5
اگر تعداد دستورات ()next شما بسیار زیاد باشد، آنگاه اجرای مثال بالا نیز تا مدت بسیار طولانی ادامه پیدا می کرد. برای جلوگیری از اجرای iteration به صورت نامحدود می توانیم از دستور StopIteration
استفاده کنیم. ما می توانیم درون متد ()__next__
شرطی را ذکر کنیم که در صورت برقراری، گردش را از کار بیندازد. به طور مثال کد بالا را ویرایش کرده ایم تا بعد از عدد 20 از کار بیفتد:
class MyNumbers: def __iter__(self): self.a = 1 return self def __next__(self): if self.a <= 20: x = self.a self.a += 1 return x else: raise StopIteration myclass = MyNumbers() myiter = iter(myclass) for x in myiter: print(x)
خروجی:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
به زبان ساده scope اینطور تعریف می شود: یک متغیر تنها از همان قسمتی قابل دسترس است که در آن ساخته/تعریف شده باشد. مبحث Scope مخصوص زبان پایتون نیست و در اکثر زبان های برنامه نویسی در دنیا وجود دارد. زبان پایتون نیز مانند زبان های دیگر دو نوع scope دارد:
بدون مقدمه به سراغ بررسی این دو Scope می رویم...
اگر متغیری را درون یک تابع تعریف کنید، دارای Scope محلی خواهد شد و تنها از درون همان تابع قابل دسترسی خواهد بود. مثال:
def myfunc(): x = 300 print(x) myfunc()
خروجی عدد 300 خواهد بود. اگر سعی کنید بیرون از این تابع به متغیر x دسترسی داشته باشید با خطا مواجه خواهید شد اما اگر یک تابع دیگر درون تابع خودمان داشته باشیم، باز هم می توانیم به x دسترسی داشته باشیم. به این مثال دقت کنید:
def myfunc(): x = 300 def myinnerfunc(): print(x) myinnerfunc() myfunc()
خروجی باز هم عدد 300 است. در واقع scope هیچ مشکلی در لایه های پایین تر ایجاد نمی کند، بلکه مشکل اصلی لایه های بالاتر هستند.
اگر متغیر شما در بدنه ی اصلی کدها نوشته شود (درون تابعی نباشد) دارای Scope سراسری خواهد بود. این نوع متغیرها از هر جایی (درون تابع یا بیرون تابع) در دسترس خواهند بود، به همین خاطر نامشان سراسری است:
x = 300 def myfunc(): print(x) myfunc() print(x)
خروجی:
300
300
سوال اول: آیا متغیرهای سراسری و محلی از هم جدا هستند؟
پاسخ: بله، در واقع اگر یک متغیر سراسری به نام X و یک متغیر محلی به نام X (نام های یکسان) داشته باشیم، مشکلی به وجود نخواهد آمد:
x = 300 def myfunc(): x = 200 print(x) myfunc() print(x)
خروجی:
200
300
سوال دوم: آیا می توان متغیرهای محلی را تبدیل به متغیرهای سراسری کرد؟
پاسخ: بله، با استفاده از کلیدواژه ی global
قبل از نام متغیر می توانیم این کار را انجام دهیم:
def myfunc(): global x x = 300 myfunc() print(x)
خروجی عدد 300 خواهد بود.
البته اگر متغیر را قبلا تعریف کرده باشید، سپس با کلیدواژه ی global
درون تابع آن را ذکر کنید، متغیر جدیدی نخواهید ساخت بلکه به همان متغیر سراسری دسترسی خواهید داشت و آن را ویرایش خواهید کرد:
x = 300 def myfunc(): global x x = 200 myfunc() print(x)
خروجی عدد 200 خواهد بود.
سعی کنید روی مبحث Scope تمرکز زیادی داشته باشید؛ بسیاری از خطاهای برنامه نویسان مبتدی به دلیل عدم توجه به scope متغیرها است. امیدوارم این قسمت درک شما نسبت به Scope را بهتر کرده باشد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.