Iterator (گردش‌گر) ها و مبحث scope

iterator در پایتون

Iterator یا گردش‌ گر چیست؟

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 ها

برای ساختن یک شیء یا کلاسِ 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

دستور StopIteration

اگر تعداد دستورات ()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 دارد:

  • محلی (local)
  • سراسری (global)

بدون مقدمه به سراغ بررسی این دو Scope می رویم...

Local Scope

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

def myfunc():
  x = 300
  print(x)

myfunc()

خروجی عدد 300 خواهد بود. اگر سعی کنید بیرون از این تابع به متغیر x دسترسی داشته باشید با خطا مواجه خواهید شد اما اگر یک تابع دیگر درون تابع خودمان داشته باشیم، باز هم می توانیم به x دسترسی داشته باشیم. به این مثال دقت کنید:

def myfunc():
  x = 300
  def myinnerfunc():
    print(x)
  myinnerfunc()

myfunc()

خروجی باز هم عدد 300 است. در واقع scope هیچ مشکلی در لایه های پایین تر ایجاد نمی کند، بلکه مشکل اصلی لایه های بالاتر هستند.

Global 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 را بهتر کرده باشد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری از مقدماتی تا پیشرفته با پایتون توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

حسن سلمانی
27 مرداد 1399
لفطا بیان کنید (iter(myclass همان ()__myclass.__iter میباشد و ()__myclass.__next میباشد و عبارت (iter(myclass خلاصه شده همان ()__myclass.__iter میباشد. و میتوانیم خودمان توابعی مانند آن بنویسیم و آنرا call کنیم.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.