ویدیو آموزش namespace در پایتون

November 2021

بعد از این ویدیو ببینید: ویدیو آشنایی عمیق با function در پایتون

 

این آموزش فضاهای نام پایتون را پوشش می دهد، ساختارهایی که برای سازماندهی نام های نمادین اختصاص داده شده به اشیاء در برنامه پایتون استفاده می شوند.

 

یک دستور انتساب یک نام نمادین ایجاد می کند که می توانید از آن برای ارجاع به یک شی استفاده کنید. عبارت x = foo یک نام نمادین x ایجاد می کند که به شی رشته 'foo' اشاره دارد.

 

در یک برنامه با هر پیچیدگی، صدها یا هزاران نام از این قبیل ایجاد خواهید کرد که هر کدام به یک شی خاص اشاره می کنند. چگونه پایتون همه این نام ها را ردیابی می کند تا با یکدیگر تداخل نداشته باشند؟

 

 

 #  Namespace در پایتون

فضای نام مجموعه ای از نام های نمادین تعریف شده در حال حاضر همراه با اطلاعاتی در مورد شیئی است که هر نام به آن ارجاع می دهد. شما می توانید فضای نام را به عنوان یک دیکشنری در نظر بگیرید که در آن کلیدها نام اشیا و مقادیر خود اشیا هستند. هر جفت کلید-مقدار یک نام را به شی متناظر خود نگاشت می کند.

 

در یک برنامه پایتون، چهار نوع فضای نام وجود دارد:

  1. Built-In
  2. Global
  3. Enclosing
  4. Local

 

اینها طول عمر متفاوتی دارند. همانطور که پایتون یک برنامه را اجرا می کند، فضای نام را در صورت لزوم ایجاد می کند و زمانی که دیگر مورد نیاز نیست، آنها را حذف می کند. به طور معمول، فضاهای نام زیادی در هر زمان وجود خواهند داشت.

 

 

 +  فضای نام Built-In در پایتون

فضای نام داخلی شامل نام تمام اشیاء داخلی پایتون است. این ها در همه زمان هایی که پایتون در حال اجرا است در دسترس هستند. می توانید اشیاء موجود در فضای نام داخلی را با دستور زیر لیست کنید:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError',
 'BaseException','BlockingIOError', 'BrokenPipeError', 'BufferError',
 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError',
 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
 'Exception', 'False', 'FileExistsError', 'FileNotFoundError',
 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError',
 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError',
 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt',
 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None',
 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError',
 'OverflowError', 'PendingDeprecationWarning', 'PermissionError',
 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning',
 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration',
 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError',
 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError',
 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__',
 '__doc__', '__import__', '__loader__', '__name__', '__package__',
 '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray',
 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex',
 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate',
 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset',
 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input',
 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list',
 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct',
 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr',
 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

 

برخی از اشیاء را در اینجا خواهید دید که ممکن است از آموزش‌های قبلی تشخیص دهید - برای مثال، استثنای StopIteration، توابع داخلی مانند max() و len()، و انواع شی مانند int و str.

 

مفسر پایتون هنگام راه اندازی فضای Built-In را ایجاد می کند. این فضای نام تا پایان مفسر باقی می ماند.

 

 

 +  فضای نام Global در پایتون

فضای نام global شامل هر نامی است که در سطح برنامه اصلی تعریف شده است. پایتون زمانی که بدنه اصلی برنامه شروع می شود، فضای نام global را ایجاد می کند و تا زمانی که مفسر خاتمه یابد، وجود دارد.

 

به طور دقیق، این ممکن است تنها فضای نام global نباشد که وجود دارد. مفسر همچنین یک فضای نام global برای هر ماژولی که برنامه شما با دستور import بارگیری می کند ایجاد می کند.

 

 

 +  فضای نام Enclosing و Local در پایتون

همانطور که در آموزش قبلی در مورد توابع آموختید، مفسر هر زمان که یک تابع اجرا می شود، فضای نام جدیدی ایجاد می کند. آن فضای نام local برای تابع است و تا زمانی که تابع خاتمه یابد وجود دارد.

 

توابع به طور مستقل از یکدیگر تنها در سطح برنامه اصلی وجود ندارند. شما همچنین می توانید یک تابع را در داخل دیگری تعریف کنید:

>>> def f():
...     print('Start f()')
...
...     def g():
...         print('Start g()')
...         print('End g()')
...         return
...
...     g()
...
...     print('End f()')
...     return
...


>>> f()
Start f()
Start g()
End g()
End f()

 

در این مثال تابع g() در بدنه f() تعریف شده است. این چیزی است که در این کد اتفاق می افتد:

 

هنگامی که برنامه اصلی، تابع f() را فرا می خواند، پایتون یک فضای نام جدید برای f() ایجاد می کند. به طور مشابه، هنگامی که f() تابع g() را فرا می خواند، g() فضای نام جداگانه خود را می گیرد. فضای نام ایجاد شده برای g() فضای نام local است و فضای نام ایجاد شده برای f() فضای نام Enclosing است.

 

هر یک از این فضاهای نام تا زمانی که تابع مربوطه آن خاتمه یابد، وجود دارد. پایتون ممکن است بلافاصله حافظه اختصاص داده شده برای آن فضاهای نام را با پایان یافتن توابع آنها بازیابی نکند، اما تمام ارجاعات به اشیاء موجود در آنها معتبر نیستند.

 

 

 #  محدوده متغیرها

وجود فضاهای نام متعدد و متمایز به این معنی است که چندین نمونه مختلف از یک نام خاص می توانند به طور همزمان در حین اجرای یک برنامه پایتون وجود داشته باشند. تا زمانی که هر نمونه در یک فضای نام متفاوت است، همه آنها به طور جداگانه نگهداری می شوند و با یکدیگر تداخلی ندارند.

 

اما این یک سوال را ایجاد می کند: فرض کنید به نام x در کد خود اشاره می کنید، و x در چندین فضای نام وجود دارد. پایتون چگونه می داند که منظور شما کدام است؟

 

پاسخ در مفهوم scope یا محدوده نهفته است. محدوده یک نام منطقه ای از برنامه است که آن نام در آن معنی دارد. مفسر این را در زمان اجرا بر اساس جایی که تعریف نام رخ می دهد و جایی که در کد به نام ارجاع می شود، تعیین می کند.

 

برای بازگشت به سوال بالا، اگر کد شما به نام x اشاره دارد، پایتون به ترتیب نشان داده شده، x را در فضای نام زیر جستجو می کند:

 

  1. Local: اگر در داخل یک تابع به x مراجعه کنید، مفسر ابتدا آن را در درونی ترین محدوده ای که محلی برای آن تابع است جستجو می کند.
  2. Enclosing: اگر x در محدوده local نباشد اما در تابعی ظاهر شود که در داخل تابع دیگری قرار دارد، مفسر در محدوده تابع محصور جستجو می کند.
  3. Global: اگر هیچ یک از جستجوهای بالا مثمر ثمر نباشد، مفسر بعدی را در محدوده global نگاه می کند.
  4. Built-in: اگر نمی تواند x را در جای دیگری پیدا کند، مفسر محدوده داخلی را امتحان می کند.

 

این قانون LEGB است که معمولاً در ادبیات پایتون نامیده می شود. مفسر یک نام را از درون به بیرون جستجو می‌کند، در محدوده محلی، محصور، سراسری و در نهایت محدوده داخلی را جستجو می‌کند:

آموزش انواع namespace در پایتون

 

اگر مفسر نامی را در هیچ یک از این مکان‌ها پیدا نکرد، پایتون یک استثنای NameError ایجاد می‌کند.

 

چندین نمونه از قانون LEGB در زیر نشان داده شده است. در هر مورد، درونی‌ترین تابع ()g سعی می‌کند مقدار متغیری به نام x را در کنسول نمایش دهد. توجه کنید که چگونه هر مثال بسته به محدوده خود مقدار متفاوتی را برای x چاپ می کند.

 

 

 +  مثال اول: تعریف واحد

در مثال اول، x تنها در یک مکان تعریف شده است. خارج از f() و g() است، بنابراین در محدوده global قرار دارد:

>>> x = 'global'

>>> def f():
...
...     def g():
...         print(x)
...
...     g()
...

>>> f()
global

 

عبارت print() در خط 6 می تواند تنها به یک x ممکن اشاره کند. این شیء x تعریف شده در فضای نام global را نمایش می دهد که رشته "global" است.

 

 

 +  مثال دوم: تعریف دوگانه

در مثال بعدی، تعریف x در دو مکان ظاهر می شود، یکی خارج از f() و دیگری در داخل f() اما خارج از g():

>>> x = 'global'

>>> def f():
...     x = 'enclosing'
...
...     def g():
...         print(x)
...
...     g()
...


>>> f()
enclosing

 

همانطور که در مثال قبلی، g() به x اشاره دارد. اما این بار دو تعریف برای انتخاب دارد:

 

طبق قانون LEGB، مفسر قبل از جستجو در محدوده global، مقدار را از محدوده enclosing می یابد. بنابراین عبارت print() در خط 7 "Enclosing" را به جای "global" نمایش می دهد.

 

 

 +  مثال سوم: تعریف سه گانه

بعد موقعیتی است که در آن x اینجا، آنجا و همه جا تعریف می شود. یک تعریف خارج از f()، تعریف دیگر داخل f() اما خارج از g() و تعریف سوم داخل g():

>>> x = 'global'

>>> def f()
...     x = 'enclosing'
...
...     def g():
...         x = 'local'
...         print(x)
...
...     g()
...

>>> f()
local

 

اکنون دستور print() در خط 8 باید بین سه احتمال مختلف تمایز قائل شود:

 

در اینجا، قانون LEGB دیکته می کند که ()g ابتدا مقدار x تعریف شده local خود را ببیند. بنابراین عبارت 'local' را نمایش می دهد.

 

 

 +  مثال چهارم: بدون تعریف

در آخر، یک مورد داریم که در آن g() سعی می‌کند مقدار x را چاپ کند، اما x در جایی تعریف نشده است. این به هیچ وجه کار نخواهد کرد:

>>> def f():
...
...     def g():
...         print(x)
...
...     g()
...

>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
  File "<stdin>", line 4, in g
NameError: name 'x' is not defined

 

این بار، پایتون x را در هیچ یک از فضاهای نام پیدا نمی کند، بنابراین دستور print() در خط 4 یک استثنا NameError ایجاد می کند.

 

 

 #  دیکشنری‌های namespace پایتون

قبلاً در این آموزش، زمانی که فضاهای نام برای اولین بار معرفی شدند، به شما گفته شد که فضای نام را به عنوان یک دیکشنری در نظر بگیرید که در آن کلیدها نام اشیا و مقادیر خود اشیا هستند. در واقع، برای فضاهای نام global و local، این دقیقاً همان چیزی است که هستند! پایتون واقعاً این فضاهای نام را به عنوان دیکشنری پیاده سازی می کند. فضای نام built-in مانند یک دیکشنری رفتار نمی کند. پایتون آن را به عنوان یک ماژول پیاده سازی می کند.

 

پایتون توابع داخلی به نام های globals() و locals() ارائه می دهد که به شما امکان می دهد به دیکشنری های فضای نام global و local دسترسی داشته باشید.

 

 

 +  فانکشن globals() پایتون

تابع داخلی () globals یک مرجع به دیکشنری فضای نام global فعلی برمی گرداند. می توانید از آن برای دسترسی به اشیاء در فضای نام global استفاده کنید. در اینجا نمونه‌ای از این است که هنگام شروع برنامه اصلی به نظر می‌رسد:

>>> type(globals())
<class 'dict'>

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}

 

همانطور که می بینید، مفسر قبلاً چندین ورودی را در globals() قرار داده است. بسته به نسخه پایتون و سیستم عامل شما، ممکن است در محیط شما کمی متفاوت به نظر برسد. اما باید مشابه باشد.

 

حالا ببینید وقتی یک متغیر را در محدوده global تعریف می کنید چه اتفاقی می افتد:

>>> x = 'foo'

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
'x': 'foo'}

 

پس از دستور انتساب x = 'foo'، یک مورد جدید در دیکشنری فضای نام global ظاهر می شود. کلید دیکشنری نام شی، x، و مقدار دیکشنری، مقدار شی، «foo» است.

 

شما معمولاً با مراجعه به نام نمادین آن، x، به این شیء به روش معمول دسترسی خواهید داشت. اما شما همچنین می توانید به طور غیر مستقیم از طریق دیکشنری فضای نام global به آن دسترسی داشته باشید:

>>> x
'foo'

>>> globals()['x']
'foo'

>>> x is globals()['x']
True

 

با مقایسه عملگر is در خط 6 تأیید می کند که اینها در واقع یک شی هستند.

 

همچنین می توانید با استفاده از تابع globals() ورودی هایی را در فضای نام global ایجاد و تغییر دهید:

>>> globals()['y'] = 100

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
'x': 'foo', 'y': 100}

>>> y
100

>>> globals()['y'] = 3.14159

>>> y
3.14159

 

عبارت در خط 1 دارای همان تأثیر عبارت انتساب y = 100 است. عبارت در خط 12 معادل y = 3.14159 است.

 

ایجاد و اصلاح اشیاء در محدوده global به این روش، زمانی که دستورهای انتساب ساده انجام می شوند، کمی دور از مسیر اصلی است. اما کار می کند، و مفهوم را به خوبی نشان می دهد.

 

 

 +  فانکشن locals() پایتون

پایتون همچنین یک تابع داخلی به نام locals() ارائه می‌کند. این شبیه به globals() است اما به جای آن به اشیاء در فضای نام local دسترسی دارد:

>>> def f(x, y):
...     s = 'foo'
...     print(locals())
...

>>> f(10, 0.5)
{'s': 'foo', 'y': 0.5, 'x': 10}

 

هنگامی که در f() فراخوانی می شود، locals() یک دیکشنری را برمی گرداند که فضای نام local تابع را نشان می دهد. توجه داشته باشید که علاوه بر متغیر محلی s تعریف شده، فضای نام local شامل پارامترهای تابع x و y نیز می شود، زیرا این پارامترها نیز محلی برای f() هستند.

 

 

 #  تغییر متغیرها خارج از محدوده در پایتون

یک تابع به هیچ وجه نمی تواند یک شی غیرقابل تغییر را خارج از محدوده محلی خود تغییر دهد:

>>> x = 20

>>> def f():
...     x = 40
...     print(x)
...

>>> f()
40

>>> x
20

 

هنگامی که f() تخصیص x = 40 را در خط 3 اجرا می کند، یک مرجع محلی جدید به یک شی عدد صحیح که مقدار آن 40 است ایجاد می کند. در آن نقطه، f() ارجاع به شی با نام x را در فضای نام global از دست می دهد. بنابراین دستور انتساب بر شی سراسری تأثیر نمی گذارد.

 

توجه داشته باشید که وقتی تابع f() دستور print(x) را در خط 4 اجرا می کند، 40 را نمایش می دهد، مقدار x محلی خودش. اما پس از خاتمه f() ، x در دامنه global هنوز 20 است.

 

یک تابع می تواند یک شی از نوع قابل تغییر را تغییر دهد که خارج از محدوده محلی آن است، اگر شی را در جای خود تغییر دهد:

>>> my_list = ['foo', 'bar', 'baz']
>>> def f():
...     my_list[1] = 'quux'
...
>>> f()
>>> my_list
['foo', 'quux', 'baz']

 

در این مورد my_list یک لیست است و لیست ها قابل تغییر هستند. f() می تواند در my_list تغییراتی ایجاد کند حتی اگر خارج از محدوده محلی باشد.

 

اما اگر f() سعی کند به طور کامل my_list را دوباره اختصاص دهد، یک شی محلی جدید ایجاد می کند و my_list جهانی را تغییر نمی دهد:

>>> my_list = ['foo', 'bar', 'baz']
>>> def f():
...     my_list = ['qux', 'quux']
...
>>> f()
>>> my_list
['foo', 'bar', 'baz']

 

این شبیه چیزی است که زمانی که f() سعی می کند یک آرگومان تابع قابل تغییر را تغییر دهد اتفاق می افتد.

 

 

 +  دستور global پایتون

اگر واقعاً نیاز به تغییر یک مقدار در دامنه global از داخل f() داشته باشید، چه؟ این در پایتون با استفاده از دستور global امکان پذیر است:

>>> x = 20
>>> def f():
...     global x
...     x = 40
...     print(x)
...

>>> f()
40
>>> x
40

 

عبارت global x نشان می دهد که در حالی که f() اجرا می شود، ارجاع به نام x به x که در فضای نام global است اشاره دارد. این بدان معناست که تخصیص x = 40 مرجع جدیدی ایجاد نمی کند. به جای آن یک مقدار جدید به x در دامنه global اختصاص می دهد.

 

همانطور که قبلاً دیدید، globals() یک مرجع به دیکشنری فضای نام global برمی‌گرداند. اگر می خواهید، به جای استفاده از یک دستور global، می توانید همان کار را با استفاده از globals():

>>> x = 20
>>> def f():
...     globals()['x'] = 40
...     print(x)
...

>>> f()
40
>>> x
40

 

دلیل زیادی برای انجام این کار وجود ندارد زیرا اعلامیه global احتمالاً هدف را واضح تر می کند. اما تصویر دیگری از نحوه عملکرد globals() ارائه می دهد.

 

اگر هنگام شروع تابع، نام مشخص شده در اعلان global در دامنه global وجود نداشته باشد، ترکیبی از دستور global و یک انتساب آن را ایجاد می کند:

>>> y
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    y
NameError: name 'y' is not defined


>>> def g():
...     global y
...     y = 20
...

>>> g()

>>> y
20

 

در این مورد، وقتی g() شروع می‌شود، شیئی به نام y در محدوده global وجود ندارد، اما g() یکی را با دستور global y در خط 8 ایجاد می‌کند.

 

همچنین می توانید چندین نام جدا شده با کاما را در یک اعلان کلی مشخص کنید:

>>> x, y, z = 10, 20, 30

>>> def f():
...     global x, y, z
...

 

در اینجا، x، y، و z همگی برای اشاره به اشیاء در محدوده global توسط عبارت global واحد در خط 4 اعلام می شوند.

 

یک نام مشخص شده در یک اعلان global نمی تواند در تابع قبل از دستور global ظاهر شود:

>>> def f():
...     print(x)
...     global x
...

  File "<stdin>", line 3
SyntaxError: name 'x' is used prior to global declaration

 

هدف عبارت  global x در خط 3 این است که ارجاعاتی به x به یک شی در محدوده global ارجاع دهد. اما عبارت print() در خط 2 به x قبل از اعلان global اشاره دارد. این یک استثناء SyntaxError را ایجاد می کند.

 

 

 +  دستور nonlocal پایتون

وضعیت مشابهی با تعاریف تابع تو در تو وجود دارد. اعلان global به یک تابع اجازه می دهد تا به یک شی در محدوده global دسترسی داشته باشد و آن را تغییر دهد. اگر یک تابع محصور شده نیاز به تغییر یک شی در محدوده محصور داشته باشد، چه؟ این مثال را در نظر بگیرید:

>>> def f():
...     x = 20
...
...     def g():
...         x = 40
...
...     g()
...     print(x)
...

>>> f()
20

 

در این مورد، اولین تعریف x در محدوده enclosing است، نه دامنه global. همانطور که g() نمی تواند مستقیماً یک متغیر را در دامنه global تغییر دهد، همچنین نمی تواند x را در محدوده تابع enclosing تغییر دهد. به دنبال تخصیص x = 40 در خط 5، x در محدوده محصور 20 باقی می ماند.

 

کلمه کلیدی global راه حلی برای این وضعیت نیست:

>>> def f():
...     x = 20
...
...     def g():
...         global x
...         x = 40
...
...     g()
...     print(x)
...

>>> f()
20

 

از آنجایی که x در محدوده تابع enclosing است، نه دامنه global، کلمه کلیدی global در اینجا کار نمی کند. پس از خاتمه ()g، x در محدوده محصور 20 باقی می ماند.

 

در واقع، در این مثال، عبارت x global نه تنها دسترسی به x را در محدوده محصور نمی کند، بلکه یک شی به نام x در محدوده global ایجاد می کند که مقدار آن 40 است:

>>> def f():
...     x = 20
...
...     def g():
...         global x
...         x = 40
...
...     g()
...     print(x)
...

>>> f()
20
>>> x
40

 

برای تغییر x در محدوده محصور از داخل g()، به کلمه کلیدی مشابه nonlocal نیاز دارید. نام‌هایی که بعد از کلمه کلیدی nonlocal مشخص شده‌اند به متغیرهایی در نزدیک‌ترین محدوده محصور اشاره می‌کنند:

>>> def f():
...     x = 20
...
...     def g():
...         nonlocal x
...         x = 40
...
...     g()
...     print(x)
...

>>> f()
40

 

بعد از دستور nonlocal x در خط 5، وقتی g() به x اشاره می کند، به x در نزدیکترین محدوده محصور اشاره می کند که تعریف آن در f() در خط 2 است. عبارت print() در انتهای f() در خط 9 تایید می کند که فراخوانی به g() مقدار x را در محدوده محصور به 40 تغییر داده است.

ارسال نظر

تلاش میکنم سوالات شما را در کمتر از یک روز پاسخ بدم

امیر محمد

November 2021

عالی بود

پاسخ به نظر


m.slri

November 2021

بسسیار عالی . ممنون از شما

پاسخ به نظر


sh

November 2021

سلام عالی
میگم من از چند نفر شنیدم که گفتن از global اصلا نباید استفاده بشه
درسته؟؟
اگه اره به چه دلیل

پاسخ به نظر


امیرحسین بیگدلو

November 2021

سلام
خیر. چرت و پرت گفتن.
global هم کاربردهای خودش رو داره.