فصل ۵: توابع — از تعریف ساده تا scope و بازگشت

scope و قاعده‌ی LEGB، global و nonlocal

هر نام کجا زندگی می‌کند؟

وقتی در کد نامی مثل rate را می‌خوانید، پایتون باید بفهمد این نام به کدام شیء اشاره دارد. پاسخ از قاعده‌ی LEGB می‌آید؛ پایتون به این ترتیب جست‌وجو می‌کند و اولین مورد را برمی‌دارد:

حرفدامنهمثال
L — Localداخل همین تابعپارامترها و متغیرهای تعریف‌شده در تابع
E — Enclosingتابع بیرونی (در توابع تودرتو)متغیرهای تابعی که این تابع را در خود دارد
G — Globalسطح ماژول (فایل)ثابت‌ها و توابع تعریف‌شده در بالای فایل
B — Built-inنام‌های داخلی پایتونlen، print، sum
TAX_RATE = 0.10                  # Global

def invoice(amount):
    discount = 0.05              # Local
    def apply():
        return amount * (1 - discount) * (1 + TAX_RATE)   # E و G
    return round(apply())

print(invoice(10_000_000))       # 10450000
print(discount)                  # NameError — discount بیرون تابع وجود ندارد

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

دام UnboundLocalError

count = 0

def register():
    count += 1          # UnboundLocalError!
    return count

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

global و nonlocal

count = 0

def register():
    global count        # «منظورم همان count سطح ماژول است»
    count += 1

def make_counter():
    n = 0
    def step():
        nonlocal n      # «منظورم n تابع بیرونی است»
        n += 1
        return n
    return step

next_id = make_counter()
next_id(); next_id()
print(next_id())        # 3

مثال دوم یک closure است: تابع داخلی متغیر تابع بیرونی را حتی پس از پایان آن «به خاطر می‌سپارد». این پایه‌ی decorator هاست که در پایان دوره به آن اشاره می‌کنیم.

چرا global بد نام است

متغیر global را هر تابعی می‌تواند عوض کند؛ پس وقتی مقدارش غلط است، نمی‌دانید مقصر کیست. تست کردن توابعی که به global وابسته‌اند هم سخت است. قاعده: ثابت‌های global (با حروف بزرگ) خوب‌اند؛ حالت (state) global بد است. حالت را از راه پارامتر بدهید و از راه return بگیرید، یا در یک کلاس (فصل ۷) بسته‌بندی کنید.

نکته‌هایی که کمتر کسی می‌داند

  • برای تغییر محتوای یک لیست یا دیکشنری global به global نیاز ندارید (orders.append(x) کار می‌کند)؛ global فقط برای انتساب دوباره‌ی نام لازم است.
  • حلقه‌ی for و بلوک if در پایتون scope جدید نمی‌سازند؛ متغیر تعریف‌شده داخل if بعد از آن هم در دسترس است.
  • globals() و locals() دیکشنری نام‌ها را برمی‌گردانند؛ برای دیباگ مفید، برای کد اصلی ممنوع.
  • closure ها متغیر را «زنده» نگه می‌دارند، نه مقدارش را؛ به همین دلیل lambda های ساخته‌شده در حلقه همه آخرین مقدار را می‌بینند (درس آخر همین فصل).
  • اگر نام یک built-in را در سطح ماژول بازتعریف کنید (مثلاً input = ...)، طبق LEGB همه‌ی توابع آن فایل نسخه‌ی شما را می‌بینند.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.