محدوده متغیرها در پایتون؛ تفاوت Local و Global

🗓 آخرین بهروزرسانی:
محدوده متغیر یا Scope مشخص میکند هر نام در کدام بخش برنامه قابل دسترسی است و پایتون هنگام استفاده از یک متغیر، مقدار آن را از کجا پیدا میکند. درک تفاوت متغیرهای Local و Global برای نوشتن توابع قابل پیشبینی، جلوگیری از تداخل نامها و رفع خطاهایی مانند NameError و UnboundLocalError ضروری است.
این مطلب، درس ششم از دوره رایگان پایتون سطح متوسط است. در درس قبلی با پارامترها و آرگومانهای توابع آشنا شدیم. اکنون بررسی میکنیم متغیرهای داخل و بیرون تابع چه محدودهای دارند و پایتون چگونه نامها را جستوجو میکند.
Scope در پایتون چیست؟
Scope یا محدوده دسترسی، بخشی از برنامه است که در آن میتوان از یک نام استفاده کرد. هر متغیر در یک محدوده ساخته میشود و لزوماً از همه بخشهای برنامه قابل دسترسی نیست.
site_name = "Dev Source"
def show_message():
user_name = "مهران"
print(user_name)
print(site_name)
show_message()در این مثال:
site_nameخارج از تابع ساخته شده و یک متغیر سراسری است.user_nameداخل تابع ساخته شده و یک متغیر محلی است.- تابع میتواند متغیر سراسری را بخواند.
- متغیر محلی فقط در محدوده همان تابع قابل استفاده است.
متغیر محلی یا Local Variable
متغیری که داخل یک تابع تعریف میشود، معمولاً محلی است. این متغیر هنگام اجرای تابع ساخته میشود و خارج از تابع در دسترس نیست.
def calculate_total():
price = 120000
quantity = 3
total = price * quantity
print(total)
calculate_total()متغیرهای price، quantity و total محلی هستند.
دسترسی به متغیر محلی خارج از تابع
def create_message():
message = "سلام"
create_message()
print(message)این کد خطای زیر را ایجاد میکند:
NameError: name 'message' is not definedمتغیر message فقط در محدوده تابع create_message وجود دارد.
پارامترهای تابع نیز محلی هستند
پارامترهای تابع مانند متغیرهای محلی رفتار میکنند:
def greet(name):
message = f"سلام {name}"
return message
print(greet("مهران"))در این تابع، هم name و هم message فقط داخل تابع قابل استفادهاند.
هر بار فراخوانی تابع، محدوده محلی مستقل میسازد
def double(number):
result = number * 2
return result
first = double(5)
second = double(20)
print(first)
print(second)هر فراخوانی مقدارهای محلی مستقل خود را دارد. مقدار number در فراخوانی نخست با فراخوانی دوم تداخل ندارد.
متغیر سراسری یا Global Variable
متغیری که در سطح اصلی فایل و خارج از توابع ساخته میشود، متغیر سراسری نام دارد:
tax_rate = 9
def show_tax_rate():
print("نرخ مالیات:", tax_rate)
show_tax_rate()
print(tax_rate)متغیر tax_rate هم در سطح اصلی برنامه و هم برای خواندن در داخل تابع قابل دسترسی است.
خواندن متغیر Global داخل تابع
برای خواندن یک متغیر سراسری نیازی به کلمه کلیدی خاصی نداریم:
currency = "تومان"
def format_price(price):
return f"{price:,} {currency}"
print(format_price(250000))تابع مقدار currency را میخواند، اما آن را تغییر نمیدهد.
ساخت متغیر همنام داخل تابع
اگر داخل تابع به نامی مقدار بدهیم، پایتون آن نام را محلی در نظر میگیرد:
language = "Python"
def change_language():
language = "JavaScript"
print("داخل تابع:", language)
change_language()
print("بیرون تابع:", language)خروجی:
داخل تابع: JavaScript
بیرون تابع: Pythonدو متغیر مستقل با نام یکسان وجود دارند. متغیر محلی، متغیر سراسری را تغییر نداده است.
سایهانداختن متغیرها یا Variable Shadowing
وقتی یک نام محلی با نام موجود در محدوده بیرونی یکسان باشد، نام محلی در داخل آن محدوده اولویت دارد. این رفتار Shadowing نامیده میشود.
status = "عمومی"
def show_status():
status = "محلی"
print(status)
show_status()
print(status)استفاده زیاد از نامهای یکسان ممکن است خوانایی برنامه را کاهش دهد. بهتر است نام متغیرها نقش آنها را واضح بیان کند.
چرا تغییر متغیر Global داخل تابع خطا میدهد؟
کد زیر در نگاه نخست منطقی به نظر میرسد:
score = 10
def increase_score():
score = score + 1
return score
print(increase_score())اما پایتون خطای زیر را ایجاد میکند:
UnboundLocalError: local variable 'score' referenced before assignmentچون داخل تابع به score مقدار داده شده است، پایتون آن را محلی فرض میکند. در سمت راست عبارت، متغیر محلی هنوز مقداری ندارد.
کلمه کلیدی global در پایتون
با کلمه کلیدی global اعلام میکنیم که نام موردنظر به متغیر سراسری اشاره دارد:
score = 10
def increase_score():
global score
score = score + 1
return score
print(increase_score())
print(score)خروجی:
11
11اکنون تابع مقدار متغیر سراسری را مستقیماً تغییر داده است.
ساخت متغیر Global از داخل تابع
def create_setting():
global theme
theme = "dark"
create_setting()
print(theme)این روش ممکن است، اما معمولاً بهتر است متغیرهای سراسری در سطح اصلی فایل بهصورت واضح تعریف شوند.
چرا استفاده زیاد از global مناسب نیست؟
متغیرهای سراسری قابل تغییر میتوانند باعث شوند نتیجه یک تابع به وضعیت پنهان برنامه وابسته باشد. پیامدهای رایج:
- تشخیص محل تغییر داده دشوار میشود.
- آزمایش تابع سختتر میشود.
- توابع به بخشهای دیگر برنامه وابسته میشوند.
- احتمال ایجاد خطا در پروژههای بزرگ افزایش مییابد.
بهجای تغییر مستقیم متغیر سراسری، اغلب بهتر است مقدار را به تابع بدهیم و نتیجه جدید را برگردانیم:
def increase_score(current_score):
return current_score + 1
score = 10
score = increase_score(score)
print(score)این تابع مستقلتر، قابل آزمایشتر و قابل استفاده مجدد است.
تغییر محتوای یک شیء سراسری
تفاوت مهمی میان اتصال دوباره یک نام و تغییر محتوای یک شیء قابل تغییر وجود دارد. تابع زیر بدون استفاده از global محتوای لیست سراسری را تغییر میدهد:
tasks = []
def add_task(task):
tasks.append(task)
add_task("مطالعه پایتون")
print(tasks)تابع نام tasks را به شیء دیگری متصل نکرده است؛ فقط محتوای همان لیست را تغییر داده است.
اما کد زیر اتصال نام را تغییر میدهد و بدون global یک متغیر محلی میسازد:
tasks = []
def reset_tasks():
tasks = []
return tasksبرای طراحی شفافتر، بهتر است داده را به تابع ارسال و نتیجه را برگردانیم:
def add_task(task_list, task):
task_list.append(task)
return task_listمحدوده متغیر در شرطها و حلقهها
در پایتون، بلوکهای if، for و while محدوده محلی جداگانه ایجاد نمیکنند:
if True:
message = "شرط اجرا شد"
print(message)for number in range(3):
result = number * 2
print(number)
print(result)نامهای ساختهشده در این بلوکها در محدوده اطراف قابل دسترسیاند. بااینحال، بهتر است به مقدار آخر متغیر حلقه وابسته نشویم مگر این رفتار عمداً موردنظر باشد.
محدوده توابع تودرتو
یک تابع میتواند داخل تابع دیگری تعریف شود:
def outer_function():
message = "متغیر تابع بیرونی"
def inner_function():
print(message)
inner_function()
outer_function()تابع داخلی میتواند متغیر تابع بیرونی را بخواند. این محدوده، Enclosing نام دارد.
کلمه کلیدی nonlocal
اگر تابع داخلی بخواهد متغیر تابع بیرونی را تغییر دهد، از nonlocal استفاده میکنیم:
def create_counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
counter = create_counter()
print(counter())
print(counter())
print(counter())خروجی:
1
2
3nonlocal به نزدیکترین متغیر موجود در محدوده تابع بیرونی اشاره میکند، نه محدوده سراسری.
تفاوت global و nonlocal
| کلمه کلیدی | محدوده هدف | کاربرد |
|---|---|---|
global | سطح سراسری ماژول | تغییر نامی که بیرون همه توابع تعریف شده است |
nonlocal | نزدیکترین تابع بیرونی | تغییر متغیر تابع محصورکننده در توابع تودرتو |
قانون LEGB در پایتون
پایتون برای یافتن یک نام، محدودهها را به ترتیب زیر بررسی میکند:
- Local: محدوده محلی تابع فعلی
- Enclosing: محدوده توابع بیرونی
- Global: سطح اصلی فایل یا ماژول
- Built-in: نامهای داخلی پایتون مانند
printوlen
name = "Global"
def outer():
name = "Enclosing"
def inner():
name = "Local"
print(name)
inner()
outer()تابع داخلی ابتدا محدوده Local را بررسی میکند و مقدار "Local" را پیدا میکند.
نمونه جستوجوی مرحلهای LEGB
name = "Global"
def outer():
name = "Enclosing"
def inner():
print(name)
inner()
outer()چون متغیر name در تابع داخلی وجود ندارد، پایتون آن را در محدوده Enclosing پیدا میکند.
محدوده Built-in
نامهایی مانند print، len، sum و list در محدوده داخلی پایتون قرار دارند.
numbers = [10, 20, 30]
print(len(numbers))نباید بدون ضرورت از نام توابع داخلی برای متغیرهای خود استفاده کنیم:
list = [1, 2, 3]
new_list = list("Python")در این حالت نام داخلی list با متغیر جدید پوشانده شده و فراخوانی دوم خطا ایجاد میکند.
خطای NameError
وقتی پایتون یک نام را در هیچکدام از محدودههای LEGB پیدا نکند، خطای NameError ایجاد میشود:
print(user_name)دلایل رایج:
- متغیر هنوز تعریف نشده است.
- نام متغیر اشتباه تایپ شده است.
- متغیر در محدوده دیگری ساخته شده است.
- تابع یا ماژول موردنظر وارد نشده است.
خطای UnboundLocalError
این خطا زمانی رخ میدهد که پایتون نامی را محلی تشخیص دهد، اما پیش از مقداردهی از آن استفاده شود:
balance = 1000
def withdraw(amount):
balance = balance - amount
return balanceراههای اصلاح:
- مقدار را بهعنوان آرگومان به تابع ارسال کنیم.
- نتیجه را با
returnبرگردانیم. - فقط در صورت نیاز واقعی از
globalاستفاده کنیم.
def withdraw(current_balance, amount):
if amount > current_balance:
return current_balance, "موجودی کافی نیست"
return current_balance - amount, "برداشت انجام شد"
balance = 1000
balance, message = withdraw(balance, 300)
print(balance, message)مشاهده محدوده با locals و globals
تابع locals() دیکشنری نامهای محدوده محلی فعلی و تابع globals() دیکشنری نامهای سراسری ماژول را برمیگرداند:
course = "Python"
def show_scope():
lesson = 6
print(locals())
show_scope()
print("course" in globals())این ابزارها بیشتر برای بررسی و اشکالزدایی مفیدند. تغییر مستقیم خروجی آنها برای مدیریت عادی متغیرها توصیه نمیشود.
ثابتهای سراسری
برای مقدارهایی که باید در سراسر برنامه خوانده شوند و تغییر نکنند، استفاده از ثابتهای سراسری قابل قبول است. طبق قرارداد، نام ثابت با حروف بزرگ نوشته میشود:
MAX_LOGIN_ATTEMPTS = 3
TAX_RATE = 0.09
APP_NAME = "Dev Source"
def calculate_tax(price):
return price * TAX_RATEحروف بزرگ مانع تغییر مقدار نمیشوند؛ فقط به برنامهنویسان اعلام میکنند که این مقدار باید ثابت در نظر گرفته شود.
توابع خالص و مدیریت بهتر Scope
تابع خالص معمولاً فقط از ورودیهای خود استفاده میکند و بدون تغییر وضعیت بیرونی، نتیجهای برمیگرداند:
def calculate_final_price(price, discount_percent, tax_rate):
discounted = price - (price * discount_percent / 100)
return discounted + (discounted * tax_rate)مزایای این سبک:
- نتیجه تابع قابل پیشبینی است.
- آزمایش تابع آسانتر است.
- وابستگی به متغیرهای سراسری کاهش مییابد.
- استفاده دوباره از تابع سادهتر میشود.
پروژه اول: مدیریت موجودی حساب بدون global
در این پروژه، موجودی حساب به تابعها ارسال و نتیجه جدید برگردانده میشود:
def deposit(balance, amount):
if amount <= 0:
return balance, "مبلغ واریز نامعتبر است"
return balance + amount, "واریز با موفقیت انجام شد"
def withdraw(balance, amount):
if amount <= 0:
return balance, "مبلغ برداشت نامعتبر است"
if amount > balance:
return balance, "موجودی کافی نیست"
return balance - amount, "برداشت با موفقیت انجام شد"
account_balance = 1000000
account_balance, message = deposit(account_balance, 250000)
print(message)
print("موجودی:", account_balance)
account_balance, message = withdraw(account_balance, 400000)
print(message)
print("موجودی:", account_balance)هیچ تابعی مستقیماً متغیر سراسری را تغییر نمیدهد. مقدار جدید بهشکل واضح به متغیر اصلی اختصاص داده میشود.
پروژه دوم: شمارنده با nonlocal
این پروژه یک تابع سازنده شمارنده ایجاد میکند:
def create_counter(start=0, step=1):
count = start
def increase():
nonlocal count
count += step
return count
def reset():
nonlocal count
count = start
return count
return increase, reset
increase_counter, reset_counter = create_counter(start=10, step=5)
print(increase_counter())
print(increase_counter())
print(reset_counter())
print(increase_counter())متغیر count نه محلی تابع داخلی است و نه سراسری؛ بلکه در محدوده تابع create_counter قرار دارد و با nonlocal تغییر میکند.
خطاها و اشتباهات رایج
۱. انتظار دسترسی به متغیر Local بیرون تابع
def calculate():
result = 20
calculate()
print(result)راهحل: مقدار را با return برگردانید.
۲. تغییر متغیر Global بدون global
count = 0
def increase():
count += 1راهحل بهتر: مقدار را به تابع ارسال کنید و نتیجه را برگردانید.
۳. استفاده زیاد از global
استفاده گسترده از global وابستگی پنهان ایجاد میکند. ورودی و خروجی توابع را صریح نگه دارید.
۴. پوشاندن نام توابع داخلی
sum = 100
print(sum([1, 2, 3]))نامهایی مانند sum، list، str و input را برای متغیرهای شخصی انتخاب نکنید.
۵. اشتباهگرفتن global و nonlocal
global به سطح ماژول و nonlocal به نزدیکترین تابع بیرونی اشاره میکند.
۶. تکیه بر متغیر حلقه پس از پایان حلقه
متغیر حلقه در پایتون باقی میماند، اما وابستگی غیرضروری به آخرین مقدار آن میتواند کد را شکننده کند.
بهترین روشهای مدیریت محدوده متغیرها
- متغیرها را تا حد امکان در کوچکترین محدوده لازم تعریف کنید.
- داده موردنیاز تابع را از طریق پارامترها ارسال کنید.
- نتیجه را با
returnبرگردانید. - از متغیرهای سراسری قابل تغییر تا حد امکان دوری کنید.
- برای ثابتهای سراسری از نامهای حروف بزرگ استفاده کنید.
- نام توابع داخلی پایتون را نپوشانید.
- از
globalوnonlocalفقط زمانی استفاده کنید که طراحی برنامه واقعاً به آن نیاز دارد. - برای متغیرهای محلی و سراسری نامهای واضح و متفاوت انتخاب کنید.
تمرینهای درس
- تابعی بنویسید که یک متغیر محلی بسازد و مقدار آن را برگرداند.
- یک متغیر سراسری تعریف کنید و آن را بدون تغییر داخل تابع نمایش دهید.
- نمونهای بسازید که متغیر محلی، نام سراسری مشابه را Shadow کند.
- کدی که خطای
UnboundLocalErrorایجاد میکند بنویسید و سپس آن را بدونglobalاصلاح کنید. - تابع افزایش امتیاز را بهصورت ورودی–خروجی طراحی کنید.
- یک ثابت سراسری برای نرخ مالیات بسازید و در تابع محاسبه مالیات استفاده کنید.
- تابع تودرتویی بنویسید که مقدار تابع بیرونی را بخواند.
- با
nonlocalیک شمارنده کاهشی بسازید. - مثالی برای چهار مرحله قانون LEGB آماده کنید.
- یک برنامه کوچک مدیریت سبد خرید بسازید که بدون متغیر سراسری قابل تغییر کار کند.
📌 همچنین بخوانید: بازار کار پایتون در ایران، دستورهای break، continue و pass در پایتون و ساخت ربات تلگرام با پایتون
پرسشهای متداول
آیا متغیر Global داخل همه فایلهای پروژه قابل دسترسی است؟
خیر. متغیر Global در سطح همان ماژول یا فایل تعریف میشود. برای استفاده در فایل دیگر باید ماژول را وارد کرد و از نام آن استفاده کرد.
آیا برای خواندن متغیر سراسری به global نیاز داریم؟
خیر. global زمانی لازم است که بخواهیم داخل تابع نام سراسری را دوباره مقداردهی کنیم.
آیا if و for محدوده جداگانه میسازند؟
خیر. در پایتون، بلوکهای شرط و حلقه محدوده تابعمانند مستقل ایجاد نمیکنند.
تفاوت NameError و UnboundLocalError چیست؟
NameError زمانی رخ میدهد که نامی پیدا نشود. UnboundLocalError زمانی رخ میدهد که نام محلی تشخیص داده شده، اما پیش از مقداردهی استفاده شود.
آیا استفاده از global همیشه اشتباه است؟
خیر، اما باید محدود و آگاهانه باشد. برای ثابتها، تنظیمات ساده یا برخی وضعیتهای سطح برنامه ممکن است مناسب باشد؛ بااینحال تغییر گسترده متغیرهای سراسری نگهداری برنامه را دشوار میکند.
nonlocal کجا استفاده میشود؟
داخل تابع تودرتو، زمانی که لازم است متغیر تابع بیرونی تغییر کند.
جمعبندی
متغیرهای محلی فقط در تابع خود قابل استفادهاند و متغیرهای سراسری در سطح ماژول تعریف میشوند. پایتون نامها را براساس قانون LEGB جستوجو میکند. برای تغییر متغیر سراسری از global و برای تغییر متغیر تابع بیرونی از nonlocal استفاده میشود؛ اما در بیشتر برنامهها بهتر است دادهها بهصورت پارامتر وارد تابع شوند و نتیجه با return بازگردد.
درس بعدی: آرگومانهای *args و **kwargs در پایتون — در حال آمادهسازی.
ادامه مسیر یادگیری پایتون
برای یادگیری منظم، آموزشهای مرتبط زیر را بهترتیب دنبال کنید:
برای ذخیره پیشرفت در همه دستگاهها و دریافت گواهی پایان مسیر، رایگان ثبتنام کن یا وارد شو.