هربرنامه پایتون در بردارنده تعداد زیادی از عناصر برنامه نویسی مانند متغیر ، تابع ، کلاس و شی است که باید به هر یک نامی داده شود تا مفسرپایتون با استفاده از آن بتواندعنصررا شناسایی و به آن دست یابد. به این نام شناسه[1] عنصر گفته می شود. برای مثال در دستور جایگزینی Age = 25 می توان گفت که 25 یک شی ذخیره شده در حافظه است و Age نامی است که به آن نگاشت شده است. به زبان ساده تر در یک برنامه پایتون هر عنصر برنامه نویسی دارای یک نام است که از آن برای شناسایی عنصر و دستیابی به آن استفاده می شود. از این رو وجود شیوه های کارآمد و موثر جهت تضمین یکتایی نام ها و چگونگی برخورد با نام های مشابه ، ضرورتی گریزناپذیر است. به این منظور پایتون از مفهومی به نام فضای نام[2] برای ساماندهی ، دسته بندی و اداره نام های موجود در یک برنامه و تضمین یکتایی آنها و چگونگی رفتاربا نام های مشابه استفاده می کند.
فضای نام مجموعه ای از شناسه ها(نامها) است که در آن هر نام به یک شی[3] نگاشت شده است به زبان ساده تر فضای نام نگاشتی از نامها به اشیاء است.فضای نام در پایتون یک مفهوم منطقی است که به صورت تودرتو و در سه سطح سازماندهی می شود هر سطح در زمان خاصی ساخته می شود و طول عمر معینی دارد. شکل زیر مفهوم تودرتو بودن فضای نام در پایتون را به تصویر می کشد.
در بالاترین سطح ، فضای نامی ذاتی (Built-in) قرار دارد این سطح با شروع مفسر پایتون به صورت خودکار ساخته می شود و تا زمانی که مفسر در حال اجراست وجود خواهد داشت. به همین دلیل است که توابعی چون print() ، input() و دیگر توابع و دستورات پیش ساخته[4] پایتون همیشه و در هر بخشی از برنامه در دسترس هستند.
دومین سطح که درون سطح اول قرار دارد فضای نام سراسری (Global) نامیده می شود. این سطح وابسته به یک فایل در بردارنده کد پایتون است یعنی با ایجاد هر فایل دارای پسوند .py فضای نام سراسری ویژه آن فایل هم ساخته می شود. لازم به یاد آوری است که برنامه های نوشته شده با پایتون در فایل هایی با پسوند .py ذخیره می شوند. هر فایل فضای نام سراسری ویژه خود را دارد که جدای از فضای نام سراسری دیگر فایل هاست ، بنابراین وجود نام های یکسان در فایل های جداگانه مجاز است.
در فرهنگ واژگان پایتون به فایلهای در بردارنده دستورات ، ساختارهای برنامه نویسی و دیگر عناصر زبان که مستقیم اجرا می شوند اسکریپت (Script) و به فایلهای در بردارنده عناصر و ساختارهایی چون متغیرها ، توابع و کلاس ها که به طور مستقیم اجرا نمی شوند و می توان کدهای نوشته شده در آنها را با استفاده از رهنمون import در یک فایل دیگر وارد و سپس فراخوانی و اجرا کرد پیمانه (Module) گفته می شود.
فضای نام سراسری هر فایل به طور کامل جدای از هر فایل دیگری است یعنی نمی توان از یک فایل به نام های موجود در دیگری دست یافت بنابراین برای دسترسی به نام های درون فضای نام سراسری مورد نیاز باید آن را به درون فایل مورد نظر خود وارد کنید که این کار با استفاده از رهنمون import انجام می شود. برای مثال فرض کنید برنامه ما دارای دو فایل به نام های calculate.py وmain.py است در فایلcalculate.py تابعی به نام Fibo() و به شکل زیر تعریف کرده ایم که می تواندn جمله اول سری فیبونانچی را تولید و چاپ کند.
def Fibo(n):
a, b = 0, 1
while(n>0):
print(a, sep=" ")
a, b = b, a + b
n -= 1
به صورت معمول این تابع از درون فایل main.py قابل دسترس نیست و استفاده از آن در این فایل بدون استفاده از رهنمون import موجب اعلام خطای زیرتوسط مفسر پایتون خواهد شد :
NameError: name 'Fib' is not defined
علت اعلام خطا این است که هردو فایل calculate.py و main.py دارای فضای نام سراسری ویژه خود هستند که به طور کامل جدای از یکدیگرند و به هم دسترسی ندارند. برای حل این مشکل کافی است همانند زیر رهنمون import را بکار گیرید در این کد چگونگی دستیابی به شناسه Fibo که به یک تابع از فضای نام calculate اشاره دارد نشان داده شده است :
import calculate
calculate.Fibo(7)
و سرانجام درونی ترین سطح ، فضای نام محلی (local) است که درون سطح سراسری قرار می گیرد. یک فضای نام محلی در زمان فراخوانی یک تابع ساخته می شود. و در بردارنده تمامی نام های موجود در بدنه تابع و نام پارامترهای آن است.به زبان ساده تر با فراخوانی هر تابع یک فضای نام محلی برای آن تابع درون فضای نام سراسری ساخته می شود و با پایان یافتن اجرای تابع از بین می رود. به همین دلیل است که می توان درون یک تابع متغیرهایی همنام با متغیر های خارج از تابع تعریف کرد. زیرا آنها دردو فضای نام جداگانه قرار دارند. وچون فضای نام محلی درون فضای نام سراسری ساخته می شود می توان از درون یک تابع به متغیرهای خارج از آن که در فضای نام سراسری هستند. دسترسی پیداکرد.
وجود فضاهای نام چندگانه و گوناگون به این معنی است که همزمان چندین نمونه مختلف از یک نام واحد می توانند در یک برنامه پایتون وجود داشته باشند وتا هنگامی که هر نمونه در یک فضای نام جداگانه زندگی می کند با یکدیگر هیچ گونه برخورد و تداخلی نخواهند داشت. برای مثال نام Average می تواند در چندین پیمانه (Module) جداگانه وجود داشته باشد. اما اگر در یک پیمانه واحد نام های مشابه وجود داشته باشند پایتون چگونه آنها را اداره می کند به بیان ساده تر پرسش این است که ساماندهی و اداره کردن نام ها درون یک فایل واحد چگونه است. به ویژه آنکه می دانیم درون یک فایل جایی است که فضاهای نام محلی دیگری همچون توابع یا کلاس ها نیز می توانند وجود داشته باشند و برای تمییز آنها از یکدیگر پیشوند خاصی همانند آنچه رهنمون import در اختیارمان می گذاشت وجود ندارد. پاسخ این پرسش در مفهومی به نام محدوده یا دامنه نهفته است. دامنه یا قلمرو یک نام ، ناحیه یا بخشی از متن کد برنامه است که در صورت نیاز و بدون استفاده از هیچ پیشوندی می توان به آن نام دسترسی داشت و ازآن استفاده کرد.بیرون از این ناحیه چنین نامی وجود ندارد و نمی توان به آن دست یافت.
بدنه و سرآیند هرتابع یک حوزه محلی[5] است. سرآیند یک تابع همان فضای بین دو کمانک بازوبسته پس از نام تابع است که در آن ورودی های ظاهری[6] تابع قرارمیگیرند ازاین رو در زبان برنامه نویسی پایتون هر متغیری که درون یک تابع تعریف شود از دید مفسر پایتون یک متغیر محلی است و دامنه دسترسی به آن محدود به همان تابع است. و بیرون از آن تابع نمی توان به آن دسترسی داشت.بر اساسآنچه که گفته شد دامنه ورودی های ظاهری تعریف شده در تابع هم محلی است.
برای مثال در کد زیرمفسر پایتون با رسیدن اجرای کد به دستور print(x) اعلام خطا خواهد کرد چرا که متغیر x درون تابع square() تعریف شده و دامنه دسترسی به آن محلی است پس با خروج از تابع از بین می رود بنابراین بیرون از تابع ناشناخته است و نمی توان به آن دسترسی داشت.
def square():
x=5
print(x * x)
square()
print(x)
#output NameError: name 'x' is not defined
در توابع تودرتو یعنی حالتی که درون یک تابع ، تابع دیگری تعریف می شود تابع درونی به متغیر تعریف شده در تابع بیرونی دسترسی دارد اما عکس آن ممکن نیست یعنی از تابع بیرونی نمی توان به متغیرمحلی تعریف شده در تابع درونی دست یافت. برای نمونه در مثال زیر متغیر n درون تابع calc() تعریف شده است بنابراین تابع بیرونی square() به این متغیر دسترسی ندارد اما تابع calc() که درون تابع square() تعریف شده است می تواند به متغیر x که در تابع بیرونی square() تعریف شده دست یابد.
مثال :
def square() :
x = 5
def calc():
n = x
return n * n
print(calc())
square()
درتابع های تودرتو در حالت عادی هرمتغیری که درون یک تابع داخلی[7] تعریف می شود از دید مفسر پایتون یک متغیر محلی است و دامنه دسترسی به آن محدود به همان تابع است. با این همه گاهی نیاز است که درون یک تابع داخلی، مقدار متغیر تعریف شده در تابع بیرونی[8] تغییر داده شود. این کار در حالت عادی ممکن نیست چرا که با دادن مقدار جدید به متغیر مورد نظر درون بدنه تابع داخلی ، یک متغیر جدید محلی همنام با متغیر موجود در تابع بیرونی ساخته می شود و مقدار جدید به آن داده می شود از این رو مقدار متغیر موجود در تابع بیرونی بدون تغییر باقی می ماند. برای چیرگی بر این چالش می توانید از رهنمون nonlocal پیش از نام متغیر دلخواه خود استفاده کنید. در یک تابع درونی، بکار گیری رهنمون nonlocal پیش از نام یک متغیر بدین معنی است که دامنه آن نه محلی است و نه سراسری بلکه محل زندگی آن در نزدیکترین محدوده در برگیرنده دامنه محلی جاری است. که در حالت توابع تودرتو نزدیکترین محدوده در برگیرنده دامنه محلی (تابع درونی) همان دامنه محلی تابع بیرونی است.
مثال: ایجاد یک متغیردر تابع درونی که همنام با متغیر موجود در تابع بیرونی است :
def outer_func() :
x=5
def inner_func():
x=7
return x * x
print(inner_func())
print(x)
outer_func()
خروجی دستور بالا به ترتیب 49 و 5 است
بازنویسی همان مثال با استفاده از رهنمون nonlocal :
def outer_func():
x=5
def inner_func():
nonlocal x
x = 7
return x * x
print(inner_func())
print(x)
outer_func()
خروجی دستور بالا 49 و 7 خواهد بود. همان گونه که دیده می شود تابع درونی مقدار متغیر تعریف شده در تابع بیرونی را تغییر داده است.
به نواحی درون یک فایل که خارج از بدنه هر تابع یا کلاسی که درون آن فایل تعریف شده است قرار دارند دامنه یا حوزه سراسری[9] گفته می شود. بسیار مهم است بدانید که واژه سراسری در بحث فضای نام و حوزه تنها به سرتاسر یک فایل اشاره می کند و نه به سراسریک برنامه.
به متغیری که درون یک فایل و خارج از هر تابع یا کلاسی تعریف می شود یک متغیر عمومی یا سراسری[10] گفته می شود. برای نمونه در مثال زیر متغیر msg یک متغیر سراسری است و از آنجا که هر تابعی می تواند به متغیر سراسری دسترسی داشته باشد از این رو دو تابع sayhello() و printmessage() بکار گرفته شده است :
msg="Hellow Wrld"
def sayhello():
print(msg)
def printmessage(mymsg):
print(mymsg)
sayhello()
printmessage(msg)
چنانچه در تابعی یک متغیر محلی همنام با یک متغیر سراسری وجود داشته باشد مفسر پایتون این متغیر را محلی در نظر خواهد گرفت و هر تغییری در مقدار آن تاثیری بر مقدار متغیر سراسری همنام آن نخواهد داشت. متغیر سراسری همنام همچنان سراسری است و مقدار خود را حفظ خواهد کرد. برای مثال در کد زیر در تابع sayhello () متغیری با نام msg همنام با متغیر سراسری msg تعریف شده است
msg = "Hellow Global World"
def sayhello ():
msg = "Hellow Local World"
print(msg)
def printmessage(mymsg):
print(mymsg)
sayhello ()#output is : Hellow Local World
printmessage(msg) # output is : "Hellow Global World"
در حالت عادی هر متغیری که درون یک تابع تعریف شود از دید مفسر پایتون یک متغیر محلی است و دامنه دسترسی به آن محدود به همان تابع است. با این همه اگر به هر دلیلی نیاز دارید که درون یک تابع یک متغیر عمومی تعریف کنید می توانید از رهنمون global پیش از نام متغیر دلخواه خود استفاده کنید. در یک تابع ، بکار گیری رهنمون global پیش از نام یک متغیر دامنه دسترسی به آن متغیر را از محلی به سراسری تغییر خواهد داد.
مثال :
def sayhello():
global msg
msg="Hellow World"
print(msg)
def printmessage(mymsg):
print(mymsg)
sayhello() # output is : Hellow World
printmessage(msg) # output is : Hellow World
print(msg) # output is : Hellow World
کاربرد دیگر رهنمون global تغییر مقدار یک متغیر سراسری از درون یک تابع است.برای این کار درتابع مورد نظر، باید متغیر سراسری را با استفاده از رهنمون global دوباره تعریف کنید وگرنه همانگونه که پیشتر گفته شد چنانچه در تابعی یک متغیر محلی همنام با یک متغیر سراسری وجود داشته باشد مفسر پایتون این متغیر را محلی در نظر خواهد گرفت و هر تغییری در مقدار آن تاثیری بر مقدار متغیر سراسری همنام آن نخواهد داشت.
مثال :
msg = "Hello World"
def sayhello():
global msg
msg="Hello My Country"
print(msg)
def printmessage(mymsg):
print(mymsg)
sayhello() # output is: Hello My Country
printmessage(msg) # output is: Hello My Country
print(msg) # output is: Hello My Country
|
هنگامی که از یک نام استفاده می کنیم ، مفسر پایتون ابتدا درونی ترین دامنه یعنی حوزه محلی را برای یافتن آن جستجو می کند ، اگر آن را در دامنه محلی نیابد نزدیک ترین دامنه ای که تعریف تابع را در برگرفته است یعنی حوزه محلی تابع بیرونی آن را در صورت وجود جستجو خواهد کرد، اگر باز هم نیابد در حوزه سراسری فایل جاری و پس از آن در حوزه نام ذاتی جستجو را ادامه خواهد داد. چنانجه جستجو نتیجه ای در بر نداشته باشد مفسر پایتون خطای Name Error را اعلام خواهد کرد. به این ترتیب جستجوی یک نام در پایتون قانون LEGB گفته می شود که از سرواژه های Local ، Enclosing ، Global و Built-In ساخته شده است مفهوم این قانون در شکل زیر به تصویر کشیده شده است :
مثال :
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
خروجی کد بالا به شکل زیر است :
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam