تعریف scope در پایتون و انواع محدوده نام

در زبان‌های برنامه‌نویسی، اسکوپ (scope) نقش مهمی در مدیریت دسترسی به متغیرها و کنترل رفتار کدها دارد. به‌طور کلی، اسکوپ تعیین می‌کند که هر متغیر در کدام بخش از کد قابل دسترسی است. شناخت این مفهوم کمک به ما کمک می‌کند تا کدهای بهینه‌تری نوشته و از بروز خطاهای ناخواسته جلوگیری کنیم. در این آموزش با مفهوم اسکوپ در پایتون و ۴ نوع آن آشنا خواهیم شد.

اگر با زبان‌های برنامه‌نویسی دیگری کار کرده باشید، ممکن است از کروشه ({}) برای مشخص کردن محدوده بلوک‌ها استفاده کرده باشید. اگر هم با این مفهوم اصلاً آشنا نیستید، در ادامه با آن آشنا خواهیم شد.

تعریف محدوده در پایتون با تورفتگی (indentation)

در پایتون، محدوده (Scope) از طریق تورفتگی (یا دندانه گذاری) مشخص می‌شود. پایتون از تورفتگی خطوط برای ایجاد بلوک‌های کد بهره می‌برد. این بلوک‌ها معمولاً شامل توابع، شرط‌ها و حلقه‌ها هستند و این‌گونه مشخص می‌شود که کدام دستورها درون محدودهٔ یک بخش خاص از کد قرار می‌گیرند.

به تابع say_hello() در قطعه کد زیر توجه کنید. در این مثال، دستور پرینت که hello را چاپ می‌کند در محدوده تابع say_hello قرار دارد، زیرا با تورفتگی از خط اول تابع متمایز شده است. اما پرینتی که SabzDanesh را چاپ کرده در محدوده این تابع نیست. اصطلاحاً به تمام دستوراتی که در با تورفتگی درون تابع نوشته می‌شوند، بدنه تابع یا بلوک کد مربوط به تابع پایتون می‌گوییم.

def say_hello():
    name = "Omid"
    print("Hello " + name)

print("SabzDanesh")

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

اسکوپ در پایتون

اسکوپ محدوده‌ای است که متغیرها در آن در دسترس هستند. متغیرها می‌توانند محلی، سراسری و حتی غیر محلی باشند. به عبارت دیگر، اسکوپ را می‌توانیم به‌عنوان مرزهایی در نظر بگیریم که مشخص می‌کنند کدام بخش از کد به کدام متغیرها دسترسی دارند.

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

  • Local (محلی): متغیرهایی که درون یک تابع پایتون تعریف می‌شوند.
  • Enclosing: متغیرهایی که درون یک تابع تو در تو تعریف شده‌اند. (معنی فارسی این کلمه، محصورکننده است!)
  • Global (سراسری): متغیرهایی که به‌طور سراسری و بیرون از توابع تعریف می‌شوند.
  • Built-in (داخلی): متغیرهای داخلی پایتون که به‌طور پیش‌فرض وجود دارند.

انواع اسکوپ در پایتون

بیایید هر یک از این ۴ مورد را به‌طور جزئی‌تر بررسی کنیم. دقت کنید که این مفاهیم در سایر زبان‌های برنامه‌نویسی هم استفاده می‌شوند؛ اما ممکن است هر زبان، رفتار خاصی با متغیرهای درون این اسکوپ‌ها داشته باشد.

اسکوپ محلی (Local Scope)

هر متغیری که درون یک تابع تعریف می‌شود به‌عنوان یک متغیر محلی شناخته خواهد شد. این متغیرها تنها درون همان تابع در دسترس بوده و نمی‌توانند از بیرون فراخوانی شوند.

در مثال زیر، متغیر x تنها درون تابع my_function() تعریف شده و دسترسی به آن از بیرون از محدوده تابع منجر به خطا می‌شود.

def my_function():
    x = 10
    print(x)

my_function()
print(x)   # raise NameError

بنابراین اگر این قطعه کد را اجرا کنیم، در ابتدا مقدار 10 در خروجی چاپ می‌شود. چون در خط پنجم my_function() را فراخوانی کرده‌ایم. اما وقتی به خط ششم برسیم، با خطای NameError مواجه خواهیم شد؛ زیرا متغیری به این نام صرفاً درون اسکوپ تابع تعریف شده و مفسر پایتون به آن دسترسی ندارد.

نتیجه فراخوانی متغیر خارج از اسکوپ محلی در python
نتیجه فراخوانی متغیر خارج از اسکوپ محلی در python
آموزش رایگان پایتون : از 0 تا پیشرفته یادگیری python به زبان ساده

آموزش رایگان پایتون : از 0 تا پیشرفته یادگیری python به زبان ساده

اسکوپ سراسری (Global Scope)

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

در قطعه کد زیر، متغیر x در سطح سراسری تعریف شده و هم از درون تابع و هم بیرون آن قابل دسترسی است.

x = 17

def my_function():
    print(x)

my_function()   # output: 17
print(x)        # output: 17

اگر بخواهیم مقدار یک متغیر سراسری را از درون تابع تغییر دهیم، باید ابتدا مشخص کنیم که منظورمان از x = 20 تعریف یک متغیر محلی نیست، بلکه می‌خواهیم متغیری که در بیرون از این اسکوپ قرار دارد را تغییر دهیم. برای این کار از کلمه کلیدی global مشابه قطعه کد زیر استفاده می‌کنیم:

x = 17

def my_function():
    global x
    x = 20

my_function()
print(x)

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

اسکوپ Enclosing

این اسکوپ در پایتون به متغیرهایی اشاره دارد که درون یک تابع تو در تو (nested function) قرار گرفته‌اند. می‌توانیم این اسکوپ را چیزی مابین اسکوپ محلی و سراسری در نظر بگیریم.

فرض کنید یک تابع داریم که درون آن یک تابع دیگر تعریف شده است. تابع اول را اصطلاحاً تابع بیرونی و دومی را تابع درونی می‌گوییم. اگر متغیری در محدوده تابع بیرونی تعریف شده باشد، داخل تابع درونی نیز در دسترس خواهد بود. به مثال زیر توجه کنید:

def outer_func():
    txt = "Test from SabzDanesh"

	def inner_func():
        print(txt)

	inner_func()

outer_func()

در قطعه کد بالا، txt درون تابع outer_func() تعریف شده اما تابع inner_func() که درون تابع اولی قرار دارد نیز می‌تواند به آن دسترسی داشته باشد.

اسکوپ داخلی (Built-in Scope)

اسکوپ Built-in به متغیرها و توابعی اشاره دارد که توسط پایتون به‌طور پیش‌فرض تعریف شده‌اند و همیشه در دسترس هستند. ما همیشه و در همه جای کد به توابع داخلی پایتون مثل len() یا print() دسترسی داریم.

در قطعه کد زیر، تابع print() از اسکوپ داخلی پایتون فراخوانی می‌شود. اگر تابع یا متغیری با همین نام در اسکوپ‌های محلی، Enclosing و سراسری وجود نداشته باشد، مفسر پایتون از تابع از پیش تعریف‌شده (built-in) استفاده خواهد کرد.

x = 5
print(x)  # built-in scope to call print funcion

هرگاه یک متغیر یا تابع در اسکوپ‌های محلی، Enclosing یا سراسری پیدا نشود، مفسر پایتون به اسکوپ Built-in مراجعه می‌کند تا ببیند آیا تابع یا متغیر مورد نظر وجود دارد یا خیر.

قانون LEGB : ترتیب جستجوی اسکوپ

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

  1. ابتدا در اسکوپ محلی جستجو می‌کند.
  2. اگر متغیر/تابع در اسکوپ محلی یافت نشد، به اسکوپ Enclosing می‌رود.
  3. اگر همچنان متغیر یا تابعی پیدا نشد، اسکوپ سراسری را بررسی می‌کند.
  4. در نهایت، در صورتی که متغیر یا تابع مورد نظر پیدا نشد، به اسکوپ Built-in مراجعه می‌کند.
ترتیب مراجعه به اسکوپ‌ها با قانون LEGB
ترتیب مراجعه به اسکوپ‌ها با قانون LEGB

سعی کنید بررسی کنید در قطعه کد زیر، در نهایت چه مقداری پرینت می‌شود و سپس پاسخ آن را ببینید. همچنین فکر کنید که دسترسی به هر کدام از متغیرهای x و y کدام یک از چهار حالتی است که در این آموزش فرا گرفتیم.

x = 11
y = 12

def outer():
    x = 21
    def inner():
        x = 31
        print(x, y)
    inner()

outer()

خروجی این کد، 31 12 است. همچنین متغیر x به‌صورت محلی و y به‌صورت سراسری فراخوانی شده است.

جمع‌بندی Scope در پایتون

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