فضای نام در پایتون (Namespace) — به زبان ساده
در این مطلب، مفهوم فضای نام در پایتون مورد بررسی قرار گرفته است. علاوه بر «فضای نام» (Namespace)، نگاشت از نامها به اشیا و «دامنه» (Scope) یک متغیر در «زبان برنامهنویسی پایتون» (Python Programming Language) نیز تشریح شده است.
نام در پایتون
افرادی که تاکنون «ذن پایتون» (The Zen of Python) را مطالعه کردهاند (افرادی که آن را نخواندهاند، میتوانند با تایپ کردن import this در مفسر پایتون، آن را مطالعه کنند)، در آخرین خط از لیست این اصل را خواندهاند که «فضای نام یک ایده فوقالعاده است، بیایید بیشتر از آن استفاده کنیم».
بنابراین، این فضاهای نام افسانهای چه چیزی هستند که این چنین تاکید به استفاده از آنها شده است؟ ابتدا باید بررسی شود که «نام» (Name) چیست. «نام» که به آن «شناساگر» (Identifier) نیز میگویند، نامی است که به یک «شی» (Object) داده میشود. هر چیزی در پایتون یک «شی» است. نام، راهی برای دسترسی داشتن به یک شی است. برای مثال، هنگامی که تخصیص a = 2 انجام میشود، ۲ یک شی است که در حافظه ذخیره میشود و a نامی است که به آن تخصیص داده میشود. میتوان آدرس (در رَم) برخی از اشیا را با استفاده از تابع توکار ()id دریافت کرد. مثال زیر در این رابطه قابل توجه است.
1# Note: You may get different value of id
2
3a = 2
4# Output: id(2)= 10919424
5print('id(2) =', id(2))
6
7# Output: id(a) = 10919424
8print('id(a) =', id(a))
در قطعه کد بالا، هر دو دستور به شی مشابهی اشاره دارند. در ادامه، مثال کمی چالشیتری مورد بررسی قرار میگیرد.
1# Note: You may get different value of id
2
3a = 2
4
5# Output: id(a) = 10919424
6print('id(a) =', id(a))
7
8a = a+1
9
10# Output: id(a) = 10919456
11print('id(a) =', id(a))
12
13# Output: id(3) = 10919456
14print('id(3) =', id(3))
15
16b = 2
17
18# Output: id(2)= 10919424
19print('id(2) =', id(2))
در توالی گامهای بالا چه اتفاقی میافتد؟ نمودار زیر به تشریح این موضوع کمک میکند.
ابتدا، شی ۲ ساخته میشود و نام a به آن تخصیص پیدا میکند. هنگامی که a = a+1 انجام میشود، شی جدید 3 ساخته میشود و حالا a به این شی جدید تخصیص پیدا کرده است. شایان توجه است که (id(a و (id(3 دارای مقدار مشابهی هستند. علاوه بر این، هنگامی که b = 2 انجام میشود، b به شی پیشین ۲ مرتبط میشود. این موضوع که پایتون مجبور نیست یک شی تکراری جدید بسازد، خیلی خوب است. ماهیت پویای الزام نام موجب قدرتمندتر شدن پایتون شده است. یک شی میتواند به هر نوع شیئی ارجاع پیدا کند.
1>>> a = 5
2>>> a = 'Hello World!'
3>>> a = [1,2,3]
همه این موارد معتبر هستند و a به سه نوع شی گوناگون در نمونههای مختلفی ارجاع دارد. تابعها نیز شی هستند، بنابراین، یک نام میتواند به آنها ارجاع داشته باشد.
1def printHello():
2 print("Hello")
3a = printHello()
4
5# Output: Hello
6a
نام مشابه a میتواند به یک تابع ارجاع داشته باشد و کاربر میتواند تابع را بدین شکل فراخوانی کند.
فضای نام در پایتون
اکنون که به چیستی نامها پرداخته شد، میتوان مفهوم فضای نام در پایتون را مورد بررسی قرار داد. به شکلی ساده میتوان گفت که فضای نام، مجموعهای از نامها است. میتوان فضای نام در پایتون را به صورت نگاشت هر نامی که توسط کاربر تعریف شده است به شی متناظر آن تعریف کرد.
فضاهای نام گوناگون میتوانند به طور همزمان وجود داشته باشند، اما از یکدیگر کاملا ایزوله هستند. یک فضای نام حاوی همه نامهای توکار هنگامی ساخته میشود که مفسر پایتون آغاز شده و تا هنگامی که کاربر از مفسر خارج نشود، وجود خواهند داشت.
به همین دلیل است که توابع توکاری مانند ()id و ()print همیشه در هر بخشی از برنامه در دسترس هستند. هر ماژول، فضای نام خود را میسازد. این فضاهای نام متفاوت، از یکدیگر ایزوله هستند. بنابراین، نام مشابهی که در ماژولهای متفاوت وجود دارد با یکدیگر دچار تصادم نمیشوند. ماژولها میتوانند توابع و کلاسهای گوناگونی داشته باشند. یک فضای نام محلی هنگامی ساخته میشود که یک تابع فراخوانی میشود که همه نامهای موجود در آن را دارد. مساله مشابهی با کلاس وجود دارد. نمودار زیر ممکن است به شفاف کردن این مفهوم کمک کند.
دامنه متغیرها در پایتون
با اینکه در پایتون فضاهای نام یکتایی تعریف شده است، امکان دارد کاربر قادر به دسترسی به همه آنها از هر بخشی از برنامه نباشد. مفهوم «دامنه» (Scope) در اینجا است که به میان میآید.
دامنه متغیر بخشی از برنامه است که از آن فضای نام به طور مستقیم بدون هیچ پیشوندی قابل دسترسی است. در هر لحظهای، حداقل سه دامنه «تو در تو» (Nested) وجود دارد.
- دامنه تابع کنونی که دارای اسامی محلی است.
- دامنه ماژول که دارای اسامی سراسری است.
- دامنه خارج از محدوده که دارای نامهای توکار است.
هنگامی که یک ارجاع درون یک تابع ایجاد میشود، نام در فضای محلی جستجو میشود، سپس در فضای سراسری و در نهایت در فضای توکار. اگر تابعی درون تابع دیگری باشد، یک دامنه جدید درون دامنه محلی توکار میشود.
1def outer_function():
2 b = 20
3 def inner_func():
4 c = 30
5
6a = 10
در اینجا، متغیر 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) کند، باید آن را به صورت غیر محلی تعریف کند. مثال زیر این موضوع را بهتر شفاف میکند.
1def outer_function():
2 a = 20
3 def inner_function():
4 a = 30
5 print('a =',a)
6
7 inner_function()
8 print('a =',a)
9
10a = 10
11outer_function()
12print('a =',a)
خروجی این برنامه به صورت زیر است.
a = 30 a = 20 a = 10
در این برنامه، سه متغیر گوناگون a در فضاهای نام جداگانهای تعریف شدهاند و از این رو قابل دسترسی هستند. در حالی که در برنامه زیر، خروجی برنامه متفاوت و به صورتی است که در ادامه آمده است.
1def outer_function():
2 global a
3 a = 20
4 def inner_function():
5 global a
6 a = 30
7 print('a =',a)
8
9 inner_function()
10 print('a =',a)
11
12a = 10
13outer_function()
14print('a =',a)
خروجی قطعه کد بالا، به صورت زیر است.
a = 30 a = 30 a = 30
در اینجا، همه ارجاعها و تخصیصها به دلیل استفاده از کلیدواژه global، به متغیر سراسری a است.
اگر نوشته بالا برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی پایتون 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 تغییر میکند و این همان مشکلی است که بسیاری با آن مواجه میشوند و فکر میکنند خروجی باید برابر با ۱۰ باشد.
با سپاس.
سلام، مطلب بسیار خوب و جامع بود
تشکر
خیلییییی ممنونم واقعا عالی توضیح دادید
مثال ها عالی بود
از شما سپاسگزارم