هر یک از پارامترهای ورودی و متغیرهایی که در زمان تعریف یک فانکشن در بدنۀ آن استفاده میشوند، در اصطلاحاً Local Scope (دامنۀ محلی) آن قرار دارند و تمامی متغیرهایی که در خارج از بدنۀ هر یک از فانکشنها تعریف میشوند متعلق به Global Scope (دامنۀ سراسری) اپلیکیشن میباشند. متغیرهایی که در هر یک از اسکوپهای لوکال و گلوبال تعریف میشوند، به ترتیب «متغیر لوکال» و «متغیر گلوبال» نامیده میشوند. همچنین لازم به یادآوری است که یک متغیر را نمیتوان به صورت همزمان در هر دو اسکوپ لوکال و گلوبال تعریف کرده و مورد استفاده قرار داد؛ به عبارت دیگر، یک متغیر یا لوکال است و یا به عنوان متغیری گلوبال شناخته میشود.
در واقع، میتوان اسکوپ متعلق به هر یک از متغیرها را مانند ظرفی در نظر گرفت که وقتی این ظرف از بین برود، دیگر هیچ اثری از محتویات داخل آن باقی نمیماند. به عبارتی، با شروع برنامه تنها یک اسکوپ گلوبال ایجاد میشود و زمانی که برنامۀ مذکور به پایان میرسد اسکوپ گلوبال نیز از بین میرود و از همین روی تمامی متغیرهای متعلق به این اسکوپ نیز از حافظۀ کامپیوتر پاک میشوند (اگر فرض کنیم که متغیرهای اسکوپ گلوبال پس از پایان یافتن برنامه از حافظۀ کامپیوتر پاک نشوند، در صورت اجرای مجدد برنامه متغیرهای مذکور آبجکتهایی که در آخرین اجرای برنامه به هر یک از آنها منتسب شده بودند را به خاطر میآوردند که مسلماً چنین چیزی مطلوب نیست.)
اسکوپ لوکال نیز زمانی ایجاد میشود که فانکشنی را فراخوانی میکنیم و متغیرهای تعریفشده در این فانکشن نیز در اسکوپ لوکال آن قرار میگیرند و زمانی که فانکشن مذکور مقداری را برمیگرداند (آبجکتی از نوع None
و یا هر آبجکت دیگری)، اسکوپ لوکال نیز از بین میرود که در صورت فراخوانی مجدد فانکشن، متغیرهای لوکال مقادیر اختصاصیافته در نوبت قبلی فراخوانی را به خاطر نخواهند داشت.
به طور کلی، یکسری قانون مهم در رابطه با اسکوپها در زبان برنامهنویسی پایتون وجود دارد که در ادامه با چند مورد از مهمترین آنها آشنا خواهیم شد.
قانون اول اینکه کدهایی که در اسکوپ گلوبال وجود دارند، نمیتوانند به متغیرهای لوکال دسترسی داشته باشند و از آنها استفاده کنند. برای مثال کد زیر را در نظر بگیرید:
def setName():
name = "Narges"
setName()
print(name)
همانطور که میبیند، فانکشنی به نام ()setName
تعریف کردهایم که در بدنۀ آن یک متغیر لوکال تحت عنوان name
وجود دارد و گفتهایم در صورت فراخوانی استرینگ «Narges» به متغیر name
منتسب شود. در ادامه فانکشن ()print
را در اسکوپ گلوبال و خارج از فانکشن ()setName
فراخوانی کرده و متغیر لوکالِ name
را به عنوان آرگومان ورودی به آن میدهیم. حال کد فوق را در فایلی به نام setName.py
ذخیره کرده و آن را اجرا میکنیم که خروجی حاصل از آن به صورت زیر میباشد:
Traceback (most recent call last):
File " D:/sokanacademy/Python/ setName.py", line 5, in
print(name)
NameError: name 'name' is not defined
همانطور که میبینید، از اسکوپ گلوبال به متغیر لوکالِ name
دسترسی نداریم و فراخوانی فانکشن ()print
با آرگومان ورودی name
منجر به ایجاد ارور میشود که به منظور رفع مشکل مذکور کافی است تا فانکشن ()print
را به داخل بدنۀ فانکشن ()setName
منتقل کنیم.
در قانون دوم به بیان این موضوع پرداخته شده است که دسترسی به متغیرهای گلوبال در اسکوپ لوکال امکانپذیر میباشد که برای درک بهتر این قانون کد زیر را مد نظر قرار میدهیم:
name = "Narges"
def printName():
print(name)
printName()
در کد فوق، ابتدا متغیری گلوبال تحت عنوان name
تعریف کرده و استرینگ «Narges» را به آن اختصاص میدهیم و در ادامه فانکشنی به نام ()printName
تعریف کردهایم که در آن گفتهایم در صورت فراخوانی، استرینگ منتسب به متغیر گلوبالِ name
را در خروجی چاپ کند. سپس کد فوق را در فایلی به نام printName.py
ذخیره کرده و آن را اجرا میکنیم که خروجی حاصل از اجرای آن به صورت زیر است:
Narges
همانطور که میبینید، از داخل اسکوپ لوکالِ فانکشن ()printName
به مقدار متغیر گلوبالِ name
دسترسی داریم و آن را به عنوان آرگومان ورودی به فانکشن ()print
دادهایم که در نهایت استرینگ منتسب به این متغیر در خروجی نمایش داده میشود.
قانون سوم اینکه از داخل اسکوپ لوکال یک فانکشن نمیتوانیم به متغیرهای تعریفشده در اسکوپ لوکال سایر فانکشنها دسترسی داشته باشیم. همانطور که اشاره کردیم، در صورت فراخوانی فانکشنها یک اسکوپ لوکال برایش ایجاد میشود و پس از پایان اجرا نیز این اسکوپ از بین میرود. برای درک بهتر این قانون، کد زیر را در نظر میگیریم:
def setName():
name = "Narges"
def getName():
name = "Puya"
setName()
return name
print(getName())
در کد فوق، فانکشنی به نام ()setName
تعریف کردهایم که در صورت فراخوانی، استرینگ «Narges» را به متغیر لوکالِ name
منتسب کرده و در سطر بعد فانکشنی دیگر تحت عنوان ()getName
تعریف کردهایم که در آن گفتهایم در صورت فراخوانی، استرینگ «Puya» به متغیر لوکالِ name
منتسب شده و در ادامه متد ()setName
فراخوانی شود و در نهایت استرینگ منتسب به متغیر لوکالِ name
در خروجی ریترن شود و در سطر آخر نیز متد ()getName
را فراخوانی کرده و خروجی حاصل از اجرای آن را به فانکشن ()print
دادهایم.
حال برنامۀ فوق را در فایلی به نام getName.py
ذخیره کرده و آن را اجرا میکنیم. اجرای این کد از فانکشن ()print
در اسکوپ گلوبال برنامه آغاز میشود و در ادامه فانکشن ()getName
به عنوان آرگومان ورودی فانکشن ()print
فراخوانی میشود که فراخوانی این فانکشن منجر به ایجاد یک اسکوپ لوکال برای آن شده و دستورات بدنۀ فانکشن مذکور به ترتیب اجرا میشوند به طوری که استرینگ «Puya» به متغیر لوکالِ name
منتسب شده و فانکشن ()setName
فراخوانی میشود. فراخوانی فانکشن ()setName
منجر به ایجاد یک اسکوپ لوکال برای این فانکشن میشود که اکنون دو اسکوپ لوکال داریم که هر یک از آنها با فراخوانی فانکشن متناظرشان ایجاد شدهاند.
با فراخوانی فانکشن ()setName
تنها دستور قرار گرفته در بدنۀ آن اجرا میشود که بدین ترتیب یک متغیر لوکال به نام name
ایجاد شده و استرینگ «Narges» به آن منتسب میشود و در ادامه آبجکتی از نوع None
ریترن شده و کار فانکشن مذکور به پایان میرسد. حال در این شرایط اسکوپ لوکال ایجادشده برای فانکشن ()setName
نیز از بین میرود و بدین ترتیب متغیر لوکالِ name
مربوط به این فانکشن از حافظۀ کامپیوتر پاک میشود.
در ادامه مفسر پایتون وارد اسکوپ لوکال فانکشن ()getName
شده و یک متغیر لوکال به نام name
ساخته و استرینگ «Puya» را به آن منتسب میکند که با اجرای آخرین دستور از این فانکشن استرینگ منتسب به متغیر لوکال name
ریترن شده و به فانکشن ()print
داده میشود تا آن را در خروجی چاپ کند. در نهایت، خروجی حاصل از اجرای کد فوق بدین ترتیب خواهد بود:
Puya
همانطور که میبینید، دستور return
در فانکشن ()getName
متغیر لوکالِ name
مربوط به اسکوپ لوکال این فانکشن را در خروجی ریترن میکند. در واقع، فانکشن ()setName
متغیری لوکال با نام مشترکِ name
دارد که با فراخوانی این فانکشن استرینگ «Narges» به این متغیر منتسب میشود اما این در حالی است که با پایان کار فانکشن ()setName
متغیر لوکالِ name
مربوط به آن نیز فراموش میشود. این موضوع در مورد فانکشن ()getName
نیز صادق است به طوری که با پایان یافتن اجرای دستورات، اسکوپ لوکال مربوطه نیز از بین رفته و متغیرهای لوکال آن از حافظۀ سیستم پاک میشوند.
قانون چهارم نیز به بیان این موضوع اختصاص دارد که در اسکوپ لوکال نمیتوان یک شناسۀ یکسان برای نامگذاری دو متغیر لوکال استفاده کرد اما این در حالی است که استفاده از شناسۀ مشابه به منظور نامگذاری متغیرهایی که یکی از آنها متعلق به اسکوپ لوکال بوده و دیگری در اسکوپ گلوبال تعریف شده است، مانعی ندارد. همانطور که دیدیم، در مثال قبل یک متغیر لوکال با نام name
و یک متغیر گلوبال هم با همین نام در برنامۀ خود داشتیم که به منظور جلوگیری از اشتباه و سردرگمی، بهتر است تا حد امکان از شناسههای یکسان حتی در اسکوپهای متفاوت استفاده نکنیم.
در واقع، اهمیت اسکوپهای لوکال در این است که اگر به دلایلی همچون مقداردهی نادرست متغیرهای لوکال خطایی در برنامه پیش آید، به راحتی میتوانیم متغیری که منجر به بروز ارور شده را بیابیم اما این در حالی است که در مورد متغیرهای گلوبال به سختی میتوانیم بفهمیم متغیر مد نظر در کدام قسمت از کدها به صورت نادرست مقداردهی شده است چرا که تمام واحدهای برنامه میتوانند روی آن تأثیرگذار باشند!
همچنین تعریف اسکوپهای لوکال در زبان برنامهنویسی پایتون منجر بدین خواهد شد تا مسئولیت برخی متغیرها را به فانکشن مد نظر در اسکوپ لوکال مربوطه واگذار کنیم که در این صورت اگر خطایی در مقداردهی هر یک از متغیرها رخ دهد، به راحتی میتوانیم بفهمیم که نقطۀ بروز خطا مربوط به فراخوانی فانکشن مذکور است و از همین روی میتوان گفت که تعریف اسکوپهای لوکال فرآیند دیباگ کردن برنامه را تسهیل میکنند و از همین روی در توسعۀ اپلیکیشن و به خصوص در برنامههای بزرگ باید تا حد ممکن از تعریف متغیرهای گلوبال اجتناب کرد.