فضای نام در پایتون (Namespace) – به زبان ساده
در این مطلب، مفهوم فضای نام در پایتون مورد بررسی قرار گرفته است. علاوه بر «فضای نام» (Namespace)، نگاشت از نامها به اشیا و «دامنه» (Scope) یک متغیر در «زبان برنامهنویسی پایتون» (Python Programming Language) نیز تشریح شده است.


نام در پایتون
افرادی که تاکنون «ذن پایتون» (The Zen of Python) را مطالعه کردهاند (افرادی که آن را نخواندهاند، میتوانند با تایپ کردن import this در مفسر پایتون، آن را مطالعه کنند)، در آخرین خط از لیست این اصل را خواندهاند که «فضای نام یک ایده فوقالعاده است، بیایید بیشتر از آن استفاده کنیم».
بنابراین، این فضاهای نام افسانهای چه چیزی هستند که این چنین تاکید به استفاده از آنها شده است؟ ابتدا باید بررسی شود که «نام» (Name) چیست. «نام» که به آن «شناساگر» (Identifier) نیز میگویند، نامی است که به یک «شی» (Object) داده میشود. هر چیزی در پایتون یک «شی» است.

نام، راهی برای دسترسی داشتن به یک شی است. برای مثال، هنگامی که تخصیص a = 2 انجام میشود، ۲ یک شی است که در حافظه ذخیره میشود و a نامی است که به آن تخصیص داده میشود. میتوان آدرس (در رَم) برخی از اشیا را با استفاده از تابع توکار ()id دریافت کرد. مثال زیر در این رابطه قابل توجه است.
در قطعه کد بالا، هر دو دستور به شی مشابهی اشاره دارند. در ادامه، مثال کمی چالشیتری مورد بررسی قرار میگیرد.
در توالی گامهای بالا چه اتفاقی میافتد؟ نمودار زیر به تشریح این موضوع کمک میکند.

ابتدا، شی ۲ ساخته میشود و نام a به آن تخصیص پیدا میکند. هنگامی که a = a+1 انجام میشود، شی جدید 3 ساخته میشود و حالا a به این شی جدید تخصیص پیدا کرده است. شایان توجه است که (id(a و (id(3 دارای مقدار مشابهی هستند. علاوه بر این، هنگامی که b = 2 انجام میشود، b به شی پیشین ۲ مرتبط میشود. این موضوع که پایتون مجبور نیست یک شی تکراری جدید بسازد، خیلی خوب است. ماهیت پویای الزام نام موجب قدرتمندتر شدن پایتون شده است. یک شی میتواند به هر نوع شیئی ارجاع پیدا کند.
همه این موارد معتبر هستند و a به سه نوع شی گوناگون در نمونههای مختلفی ارجاع دارد. تابعها نیز شی هستند، بنابراین، یک نام میتواند به آنها ارجاع داشته باشد.
نام مشابه a میتواند به یک تابع ارجاع داشته باشد و کاربر میتواند تابع را بدین شکل فراخوانی کند.
فضای نام در پایتون
اکنون که به چیستی نامها پرداخته شد، میتوان مفهوم فضای نام در پایتون را مورد بررسی قرار داد. به شکلی ساده میتوان گفت که فضای نام، مجموعهای از نامها است. میتوان فضای نام در پایتون را به صورت نگاشت هر نامی که توسط کاربر تعریف شده است به شی متناظر آن تعریف کرد.
فضاهای نام گوناگون میتوانند به طور همزمان وجود داشته باشند، اما از یکدیگر کاملا ایزوله هستند. یک فضای نام حاوی همه نامهای توکار هنگامی ساخته میشود که مفسر پایتون آغاز شده و تا هنگامی که کاربر از مفسر خارج نشود، وجود خواهند داشت.
به همین دلیل است که توابع توکاری مانند ()id و ()print همیشه در هر بخشی از برنامه در دسترس هستند. هر ماژول، فضای نام خود را میسازد. این فضاهای نام متفاوت، از یکدیگر ایزوله هستند. بنابراین، نام مشابهی که در ماژولهای متفاوت وجود دارد با یکدیگر دچار تصادم نمیشوند. ماژولها میتوانند توابع و کلاسهای گوناگونی داشته باشند. یک فضای نام محلی هنگامی ساخته میشود که یک تابع فراخوانی میشود که همه نامهای موجود در آن را دارد. مساله مشابهی با کلاس وجود دارد. نمودار زیر ممکن است به شفاف کردن این مفهوم کمک کند.

دامنه متغیرها در پایتون
با اینکه در پایتون فضاهای نام یکتایی تعریف شده است، امکان دارد کاربر قادر به دسترسی به همه آنها از هر بخشی از برنامه نباشد. مفهوم «دامنه» (Scope) در اینجا است که به میان میآید.
دامنه متغیر بخشی از برنامه است که از آن فضای نام به طور مستقیم بدون هیچ پیشوندی قابل دسترسی است. در هر لحظهای، حداقل سه دامنه «تو در تو» (Nested) وجود دارد.
- دامنه تابع کنونی که دارای اسامی محلی است.
- دامنه ماژول که دارای اسامی سراسری است.
- دامنه خارج از محدوده که دارای نامهای توکار است.
هنگامی که یک ارجاع درون یک تابع ایجاد میشود، نام در فضای محلی جستجو میشود، سپس در فضای سراسری و در نهایت در فضای توکار. اگر تابعی درون تابع دیگری باشد، یک دامنه جدید درون دامنه محلی توکار میشود.
در اینجا، متغیر a در فضای نام سراسری است. متغیر b در فضای نام محلی ()outer_function و c در فضای نام محلی تو در توی ()inner_function قرار دارد. هنگامی که برنامه در ()inner_function است، c محلی محسوب میشود، ولی b غیر محلی است و a سراسری محسوب میشود. میتوان مقادیر را همانطوری خواند که به c تخصیص پیدا میکنند، اما فقط میتوان b و a را از ()inner_function خواند.
اگر برای تخصیص یک مقدار به b تلاش شود، متغیر جدید b در فضای نام محلی ساخته می شود که با متغیر غیر محلی b متفاوت است. مسالهای مشابه هنگامی به وقوع میپیوندد که مقداری به a تخصیص داده میشود. اگرچه، اگر a به عنوان متغیر سراسری تعریف شود، همه ارجاعها و تخصیصها به a سراسری میروند. اگر کاربر قصد داشته باشد که متغیر b را «باز انقیاد» (rebind) کند، باید آن را به صورت غیر محلی تعریف کند. مثال زیر این موضوع را بهتر شفاف میکند.
خروجی این برنامه به صورت زیر است.
a = 30 a = 20 a = 10
در این برنامه، سه متغیر گوناگون a در فضاهای نام جداگانهای تعریف شدهاند و از این رو قابل دسترسی هستند. در حالی که در برنامه زیر، خروجی برنامه متفاوت و به صورتی است که در ادامه آمده است.
خروجی قطعه کد بالا، به صورت زیر است.
a = 30 a = 30 a = 30
در اینجا، همه ارجاعها و تخصیصها به دلیل استفاده از کلیدواژه global، به متغیر سراسری a است.
چند مثال درباره فضای نام در پایتون
در این بخش از مطلب، با چند مثال مختلف روش کار با فضای نام در پایتون را نشان دادهایم.
با بررسی این مثالها بهتر میتوانید مفهوم فضای نام را متوجه بشوید.

مثال اول: مقایسه متغیر محلی و سراسری
در این مثال دو متغیر مجزا به نام a تعریف کردهایم. متغیر اول در بیرون از تابع foo() - فضای نام سراسری - تعریف شده است. مقدار آن متغیر برابر با 3 است. متغیر دوم در داخل تابع foo() - فضای نام محلی - تعریف شده و مقدار آن برابر با 4 است. بعد از اجرا شدن تابع، دستور print(a) مقدار متغیر a داخل تابع، یعنی 4 را چاپ میکند. اما بعد از پایان کار تابع، دستور print(a)، همان مقدار بیرونی یعنی 3 را نمایش میدهد.
بعد از اجرای کد بالا توسط مفسر پایتون، خروجی به صورت زیر در کنسول، نمایش داده میشود.
4
3مثال دوم: استفاده از متغیر سراسری درون تابع
در این مثال، ابتدا متغیر a با مقدار 0 در فضای بیرون از تابع تعریف شده است. سپس تابع foo() را تعریف کردهایم. در تعریف متغیر a از کلمهی کلیدی global استفاده میکنیم. با این کار به مفسر پایتون میگوییم که متغیر a از نوع متغیرهای سراسری است. یعنی آن که a همان متغیر تعریف شده در فضای بیرونی است. در نتیجه وقتی در تابع مقدار a را برابر با 4 میکنیم، مقدار متغیر بیرونی تغییر میکند. یعنی با آن که در داخل تابع مقدار 4 چاپ میشود. اما در بیرون تابع هم مقدار متغیر سراسری تغییر کرده است. بنابراین در بیرون تابع هم عدد 4 نمایش داده میشود.
بعد از اجرای کد بالا توسط پایتون، خروجی به صورت زیر در کنسول، نمایش داده میشود.
4
4مثال سوم: توابع تو در تو
در این مثال سه متغیر a داریم که در سه فضای مختلف قرار گرفتهاند. در خط اول، متغیر a با مقدار 0 در فضای نام global تعریف شده است. داخل تابع foo1() ، دوباره متغیر a را این بار با مقدار 1 تعریف میکنیم. این متغیر در فضای نام تابع foo1() قرار دارد. بنابراین متغیر محلی است. سپس تابع foo2() را به شکل تو در تو نوشتهایم. در داخل این تابع هم متغیر محلی دیگری به نام a و با مقدار 2 تعریف میکنیم.
بعد از اجرای کد بالا، خروجی به صورت زیر در کنسول پایتون، نمایش داده میشود.
2
1
0
Traceback (most recent call last):
File "c:\Users\aryaman\Desktop\example3.py",
line 13, in
foo2()
NameError: name 'foo2' is not defined
در فهرست پایین، روش کار این کدها را نوشتهایم.
- در خط ۹، ابتدا تابع foo2() صدا زده میشود. در این تابع مقدار a برابر 2 چاپ میشود.
- سپس مقدار a در foo2() یکی زیاد میشود. ولی این تغییر فقط در همان تابع است و تاثیری روی متغیرهای بیرون از تابع ندارد.
- بعد از پایان کار تابع foo2()، مقدار a مربوط به تابع foo1() چاپ میشود. مقدار این متغیر برابر با 1 است.
- بعد از پایان اجرای تابع foo1()، این بار، مقدار a موجود در فضای نام سراسری چاپ میشود. مقدار این متغیر را در اول برنامه، برابر با 0 تعریف کردهایم.
- در آخر هم تابع foo2() را مستقیم صدا میزنیم. اما اجرای این تابع با خطا روبهرو میشود. چون foo2() داخل تابع foo1() تعریف شده است. درنتیجه، بیرون از آن قابل دسترسی نیست.
مثال چهارم: دستکاری متغیرهای nonlocal
در این مثال، میخواهیم روش استفاده از کلمه کلیدی nonlocal را بر روی متغیرها نشان بدهیم. در خط اول، متغیر a از نوع متغیرهای «سراسری» (Global) است. مقدار 0 را به این متغیر اختصاص دادهایم. بعد از آن تابع foo1() را تعریف میکنیم. داخل آن تابع هم متغیر محلی a ساخته میشود. این متغیر، متعلق به فضای نام foo1() است.

در داخل foo1() تابع foo2() به صورت تو در تو، تعریف شده است. در این تابع با استفاده از عبارت nonlocal a اعلام میکنیم که متغیر a، متعلق به foo1() است. این متغیر نه محلی است و نه گلوبال.
بعد از اجرای کد بالا، خروجی تولید شده، به صورت زیر در کنسول پایتون، نمایش داده میشود.
2
3
0
در فهرست پایین تمام کارهای انجام شده در کدهای بالا را خط به خط توضیح دادهایم.
- در خط اول، متغیر a به صورت سراسری تعریف شده است.
- از خط ۲ تا خط ۱۰ تابع foo1() و تابع foo2() به صورت تو در تو تعریف شدهاند.
- در خط ۳ متغیر a را با مقدار 1، در فضای نام تابع foo1() تعریف کردیم.
- بعد از آن، تابع foo2() تعریف شده است.
- داخل تابع foo2() از عبارت nonlocal a استفاده میکنیم. با کمک این عبارت به متغیر a دسترسی پیدا کردیم که در تابع foo1() تعریف شده است.
- در خط بعد عبارت a = 2 مقدار متغیر a داخل تابع foo1() را تغییر میدهد.
- دستور print(a) در foo2() مقدار 2 را چاپ میکند.
- در خط ۸ تابع foo2() را فراخوانی میکنیم.
- در خط بعدی هم با کمک دستور print(a) مقدار a را چاپ میکنیم. اما این بار عدد 3 چاپ میشود.
- در آخر هم تابع foo1() را فراخوانی کردهایم.
- بعد از اجرای این تابع، کد print(a)، مقدار a متعلق به فضای نام سراسری را چاپ میکند. این مقدار همچنان 0 است و تغییری نکرده.
مثال پنجم: استفاده از کلمات کلیدی به عنوان نام متغیر
در زبان برنامه نویسی پایتون میتوانیم از کلمات کلیدی به عنوان شناسه یا نام متغیر هم استفاده کنیم. این مسئله کمی سردرگم کننده است. برای همین بیشتر زبانهای برنامه نویسی چنین امکانی را به کاربران خود نمیدهند. البته در اجرای پروژههای واقعی پایتون، برنامه نویسان از این روش استفاده نمیکنند. زیرا این کار باعث کاهش خوانایی کدها میشود. اما با کمک این روش میتوان به شکل خوبی فضای نام و دامنه کدها را توضیح داد. در کدهای پایین از کلمات کلیدی در فضای نام محلی استفاده کردهایم.
بعد از اجرای کد بالا، خروجی تولید شده، به صورت زیر در کنسول پایتون، نمایش داده میشود.
3
4
123
در فهرست پایین، روش کار کدهای بالا را توضیح دادهایم:
- داخل تابع foo() دو متغیر به نامهای int و input تعریف شدهاند. به ترتیب مقادیر 3 و 4 را به این متغیرها اختصاص میدهیم.
- وقتی تابع فراخوانی میشود، مقدارهای این دو متغیر چاپ میشوند.
- در پایان هم با کمک دستور print(int("123")) عملکرد تابع int() را نشان دادهایم.
نکته مهم آن است که این متغیرها فقط داخل تابع foo() وجود دارند و بعد از پایان تابع، دیگر تاثیری در عملکرد برنامه ندارند. به همین دلیل وقتی بعد از اجرای تابع دستور print(int("123")) را مینویسیم، این کد درست کار میکند و رشته "123" به عدد 123 تبدیل میشود. چون در فضای بیرون از تابع foo()، کلمه کلیدی int نام تابع اصلی تبدیل رشته به عدد است، نه متغیری که داخل تابع تعریف کرده بودیم.
اگر نوشته بالا برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی پایتون Python
- آموزش تکمیلی برنامهنویسی پایتون
- مجموعه آموزشهای دادهکاوی و یادگیری ماشین
- زبان برنامهنویسی پایتون (Python) — از صفر تا صد
- یادگیری علم داده (Data Science) با پایتون — از صفر تا صد
- آموزش پایتون (Python) — مجموعه مقالات جامع وبلاگ فرادرس
- کاربرد پایتون چیست و با آن چه میتوان کرد؟ | راهنمای کاربردی
^^













با سپاس فراوان بخاطر توجهی که به مخاطبین خود دارید و خاصه به دلیل بذل توجه ای که به سوال پیش آمده برای من نمودید توضیحات شما کاملا روشنگر و آموزنده بود ممنون.اگه ممکنه یه مقاله در مورد نحوه خوانش و اجرای برنامه های پایتون توسط سیستم و کلا تعاملاتی که بین برنامه ؛مفسر و سیستم رخ میدهد بصورت ساده و روان (مثل همیشه ) ارایه بفرمایید مطمننا برای بسیاری از علاقه مندان مبتدی پایتون همچون من جالب خواهد بود و سپاسگذار خواهیم شد و چنانچه مقاله ای هم هست لطفا راهنمایی بفرمایید
سلام ضمن تشکر از مطلب مفیدی که ارایه کردید در مورد مثال آخر آیا اشتباها a=30 قرارداده شده و باید برابر 10 می بوده یا بنده متوجه نحوه کاربرد کلیدواژه global نشدم سپاس و صد درود
با سلام؛
از همراهی شما با مجله فرادرس سپاسگزاریم. برای مشاهده خروجی قطعه کدهایی که به عنوان نمونه در مطالب ارائه شدهاند، توصیه میشود حتما آنها را در یک محیط توسعه یکپارچه، مفسر آنلاین یا هر گزینه دیگری که مد نظر شماست اجرا کنید و خروجی را مشاهده کنید. ژوپیتر نوتبوک نیز گزینه بسیار خوبی برای این کار است. اجرای کدها توسط خود شما به یادگیری بهتر مطالب کمک و در عین حال، از ایجاد ابهام در ذهن شما جلوگیری میکند. همچنین، با انجام این کار میتوانید اطمینان حاصل کنید که خروجی دریافتی شما با آنچه در آموزش ارائه شده مطابقت دارد یا نه و در صورت عدم مطابقت، چرایی آن را جستجو کنید.
برای مثال ارائه شده در این مطلب، خروجی همانطور که در مطلب نیز گفته شده برابر با a=30 است. با اجرای برنامه، شما این خروجی را مشاهده خواهید کرد. در ادامه، توضیحات بیشتری پیرامون چرایی a=30 شدن بیان شده تا شما را در درک بهتر مطلب یاری کند. همچنین، توضیحات بیشتری پیرامون کلیدواژه global داده شده که مطالعه آن بسیار کاربردی خواهد بود.
احتمالا، دلیل اینکه شما به اشتباه بر این باور هستید که خروجی باید به صورت a=10 باشد این است که فکر میکنید پایتون کد را خط به خط از بالا به پایین میخواند و اجرا میکند. اما در واقع، پایتون یک بار کل کد را میخواند و هر جا تعریف تابع و کلاس هست آن را در حافظه نگه میدارد و چیزی را اجرا نمیکند.
سپس، کد را میخواند و از هر جا که با کلاس یا def شروع نشده، شروع به اجرای کد میکند. در این مثال، اجرای برنامه از قسمت انتهایی کد و از جایی که به a = 10 میرسد شروع میشود و پس از آن، وقتی به outer_function() میرود به دلیل آنکه a به صورت سراسری (global) تعریف شده مقدار آن را (با توجه به تابع) تغییر داده و عدد ۳۰ را چاپ میکند و از برنامه خارج میشود.
نکاتی پیرامون استفاده از کلیدواژه global
استفاده از کلیدواژه global نهی شده است. دلیل این امر آن است که در روزهای ابتدایی معرفی پایتون، کلیدواژههای global و nonlocal معرفی شدند ولی این موارد با فلسفه کلی پایتون که تاکید بر صریح بودن (Explicit) همه چیز دارد، تطابق نداشتند. شعار زیر در بین هواداران پایتون بسیار محبوب و شناخته شده است:
صریح (بودن) بهتر از ضمنی (بودن) است. (Explicit is better than implicit)
اما این کلیدواژهها و به طور خاص global حذف نیز نشدند و دلیل عدم حذف، استفاده برنامههای متعدد از آن بود. بنابراین توصیه شده است که تا جای ممکن از آن استفاده نشود زیرا صراحت برنامه را دچار اشکال میکند و امکان داشتن خطا را افزایش میدهد. یعنی اگر چیزی در سراسر برنامه قرار است تغییر کند، باید به طور واضح مشخص باشد که یک خروجی در یک متغیر برمیگردد، نه اینکه یک متغیر در خود تابع عوض شود.
در مثالی که در همین مطلب ارائه شده است نیز اگر برنامهنویس داخل تابع را نبیند، متوجه نمیشود که مقدار a تغییر میکند و این همان مشکلی است که بسیاری با آن مواجه میشوند و فکر میکنند خروجی باید برابر با ۱۰ باشد.
با سپاس.
سلام، مطلب بسیار خوب و جامع بود
تشکر
خیلییییی ممنونم واقعا عالی توضیح دادید
مثال ها عالی بود
از شما سپاسگزارم