یک بار، در جلسه‌ی دیباگ با یک تیم، توسعه‌دهنده‌ای کدی نوشته بود که در محیط لوکال بی‌نقص اجرا می‌شد ولی در محیط production با یک خطای ساده رد می‌شد: NameError: name "os" is not defined. مشکل نه در کد بود، نه در محیط؛ در یک import بود که در یک فایل کمکی جا افتاده بود. آن روز فهمیدم که خطای NameError در Python، در نگاه اول یک خطای ساده به‌نظر می‌رسد، ولی در باطن، یک پنجره به سمت مدیریت فضاهای نام، وابستگی‌ها، و انضباط کد است.

خطای NameError در Python دقیقاً چیست؟

Python یک زبان مبتنی بر namespace است. هر نامی که در کد شما ظاهر می‌شود - نام متغیر، نام تابع، نام کلاس، نام ماژول - باید پیش از استفاده، در یکی از namespaceهای فعال تعریف شده باشد. وقتی کد شما به نامی اشاره کند که در هیچ namespace فعالی وجود ندارد، Python استثنای زیر را مطرح می‌کند:

Traceback (most recent call last):
  File "script.py", line 5, in <module>
    print(user_name)
NameError: name "user_name" is not defined

این خطا از نوع Exception است، نه SyntaxError. یعنی در زمان اجرا رخ می‌دهد، نه در زمان parse. این تفاوت مهم است چون در کدهای بزرگ، بخش‌هایی که اجرا نمی‌شوند ممکن است NameError داشته باشند بدون اینکه مشکلی ایجاد کنند.

پیام خطا سه داده‌ی مهم دارد: نامی که یافت نشد (در نقل‌قول)، مسیر فایل، و شماره خط. ترکیب این سه، جهت تشخیص را تعیین می‌کند. اگر با مبانی Python آشنایی ندارید، ابتدا آموزش پایتون از صفر را بخوانید تا مدل ذهنی درستی از نام‌ها و namespaceها شکل بگیرد.

NameError یک شکایت از نام نیست، یک شکایت از ترتیب و ساختار است. این خطا می‌گوید: این نام، در لحظه‌ای که به آن نیاز داشتی، در هیچ namespace فعالی وجود نداشت.

مفهوم namespace در Python

برای درک درست NameError، باید مفهوم namespace را بشناسیم. namespace یک فضای ذخیره‌سازی برای نام‌ها است. در Python، چهار نوع namespace وجود دارد:

یک: Built-in namespace. شامل توابع و کلاس‌های داخلی Python مثل print، len، Exception. این namespace همیشه فعال است.

دو: Global namespace. شامل نام‌های سطح ماژول. هر فایل Python، یک namespace گلوبال دارد.

سه: Enclosing namespace. در توابع تودرتو، توابع داخلی به متغیرهای تابع بیرونی دسترسی دارند. این namespace، به آن enclosing گفته می‌شود.

چهار: Local namespace. شامل متغیرهای محلی داخل یک تابع. این namespace، در زمان فراخوانی تابع ساخته می‌شود و در زمان پایان، از بین می‌رود.

Python برای پیدا کردن یک نام، این چهار namespace را به ترتیب از داخل به بیرون جستجو می‌کند. این ترتیب به قانون LEGB معروف است: Local → Enclosing → Global → Built-in. اگر نامی در هیچ‌کدام پیدا نشود، NameError مطرح می‌شود.

درک این ترتیب، کلید تشخیص NameError است. خیلی از NameErrorها از این می‌آید که کد شما انتظار داشته نامی در یک namespace باشد، ولی در واقع در آنجا نبوده.

چرا Python این خطا را مطرح می‌کند؟

Python به‌طور طراحی‌شده، نام‌های نامعین را به‌جای فرض‌کردن مقدار پیش‌فرض، مستقیماً رد می‌کند. این تصمیم، از یک فلسفه می‌آید: صراحت بهتر از ابهام است. اگر Python به‌جای NameError مقدار None برمی‌گرداند، بسیاری از باگ‌ها پنهان می‌ماندند و در جای دیگری ظاهر می‌شدند.

سه دلیل بنیادین برای این طراحی:

یک: پیشگیری از باگ‌های پنهان. اگر نامی تعریف نشده باشد، احتمالاً یک اشتباه در کد وجود دارد. Python ترجیح می‌دهد این اشتباه را زودتر افشا کند تا در مرحله‌ی بعدی، باگ‌های پیچیده‌تری ایجاد شود.

دو: وضوح کد. وقتی Python مجبور است که هر نام را صریح تعریف کنید، خواننده‌ی کد سریع‌تر می‌فهمد که چه چیزی کجا تعریف شده. این وضوح، در پروژه‌های بزرگ حیاتی است.

سه: انضباط محیط. در محیط‌هایی مثل interactive shell، پایتون اجازه می‌دهد که نام‌ها در لحظه تعریف شوند. ولی در اسکریپت‌های اجرایی، Python انتظار دارد که نام‌ها پیش از استفاده تعریف شده باشند. این تفاوت، از یک اصل بنیادین می‌آید: کد اجرایی باید صریح و قابل پیش‌بینی باشد.

در چارچوب کلی Python، NameError بخشی از ساختار دفاعی زبان است. این خطا، شما را وادار می‌کند درباره‌ی ساختار کد و ترتیب تعریف‌ها صریح باشید. همین فلسفه در سایر خطاهای Python مثل خطای IndentationError در Python و خطای KeyError در پایتون هم دیده می‌شود.

پنج علت رایج خطای NameError

در تجربه‌ی من روی صدها پروژه‌ی Python، NameError از پنج علت مشخص می‌آید. شناخت این علت‌ها، تشخیص را در چند ثانیه ممکن می‌کند.

  1. اشتباه تایپی در نام متغیر یا تابع: شایع‌ترین علت.
  2. مشکلات scope: استفاده از متغیر خارج از محدوده‌ی دید آن.
  3. import فراموش‌شده یا نادرست: نبود ماژول یا کلاس در namespace فعلی.
  4. ترتیب تعریف توابع و کلاس‌ها: استفاده قبل از تعریف.
  5. UnboundLocalError: نوع خاصی از NameError در توابع.

هر علت، نشانه‌های مخصوص به خود و راه‌حل اختصاصی دارد. در بخش‌های بعدی، هر علت را جداگانه باز می‌کنم.

اشتباه تایپی در نام متغیر یا تابع

شایع‌ترین علت NameError، اشتباه تایپی در نام است. مثلاً نوشتن userName به‌جای user_name، یا pritn به‌جای print. این اشتباهات در ظاهر ساده به‌نظر می‌رسند ولی در IDEهایی که auto-completion ضعیف دارند، زیاد دیده می‌شوند.

Python به بزرگی و کوچکی حروف حساس است. یعنی name، Name، و NAME سه نام متفاوت هستند. این حساسیت، منبع بی‌پایان NameError است.

راه‌حل: استفاده از IDEهای پیشرفته که auto-completion قوی دارند. ابزارهایی مثل PyCharm و VS Code، نام‌های تعریف‌شده را در autocomplete نشان می‌دهند و از اشتباهات تایپی پیشگیری می‌کنند. علاوه بر این، ابزارهای تحلیل ایستا مثل pylint و flake8، نام‌های نامعین را قبل از اجرا تشخیص می‌دهند.

مشکلات scope: متغیرهای محلی و گلوبال

شایع‌ترین منبع NameErrorهای پیچیده، مربوط به scope است. Python از قانون LEGB برای جستجوی نام‌ها استفاده می‌کند. اگر کد شما انتظار داشته نامی در یک scope باشد ولی در واقع در آن scope نباشد، NameError می‌گیرید.

def process_data():
    user_name = "Ali"

process_data()
print(user_name)  # NameError: name "user_name" is not defined

در این مثال، user_name در local namespace تابع تعریف شده است. بعد از پایان تابع، این namespace از بین می‌رود و نام user_name دیگر وجود ندارد.

راه‌حل: یا متغیر را در global namespace تعریف کنید، یا مقدار آن را از تابع بازگردانید:

def process_data():
    return "Ali"

user_name = process_data()
print(user_name)

نکته‌ی ظریف: در Python، توابع تودرتو می‌توانند به متغیرهای تابع بیرونی دسترسی داشته باشند (enclosing namespace)، ولی نمی‌توانند به‌طور پیش‌فرض آن را تغییر دهند. این موضوع در بخش `global` و `nonlocal` به‌تفصیل بررسی شده است.

import فراموش‌شده یا نادرست

یکی دیگر از منابع شایع NameError، import فراموش‌شده است. Python، برخی از ماژول‌ها را به‌طور خودکار بارگذاری می‌کند (مثل builtins) ولی بخش بزرگی از کتابخانه‌های استاندارد، نیاز به import صریح دارند.

# اشتباه - os ایمپورت نشده
path = os.path.join("home", "user")

# درست
import os
path = os.path.join("home", "user")

پیام خطا در این حالت صریح است: NameError: name "os" is not defined. راه‌حل: اضافه کردن import در بالای فایل.

شکل دیگری از این خطا، وقتی است که از یک import ناقص استفاده می‌کنید:

# اشتباه - path از os ایمپورت نشده
from os import path
current = os.getcwd()  # NameError

# درست - یا os کامل ایمپورت شود
import os
current = os.getcwd()

نکته‌ی حرفه‌ای: برای اجتناب از این نوع خطا، در پروژه‌های بزرگ از imports صریح استفاده کنید و از from module import * خودداری کنید. این رویکرد، خوانایی و پایداری کد را افزایش می‌دهد.

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

ترتیب تعریف توابع و کلاس‌ها

در Python، توابع و کلاس‌ها باید پیش از فراخوانی تعریف شده باشند. برخلاف بعضی زبان‌های دیگر که forward declaration پشتیبانی می‌کنند، Python در این زمینه سختگیر است.

# اشتباه - فراخوانی قبل از تعریف
greet()
def greet():
    print("Hello")

# درست
def greet():
    print("Hello")
greet()

این نکته در ماژول‌ها و فایل‌های چندگانه، پیچیده‌تر می‌شود. اگر ماژول A به تابعی از ماژول B وابسته است که آن هم به ماژول A وابسته است، ممکن است در زمان import با NameError مواجه شوید. این پدیده که Circular Import نامیده می‌شود، در خطای ImportError در پایتون به‌تفصیل بررسی شده است.

راه‌حل: در موارد Circular Import، از imports تأخیری (lazy imports) استفاده کنید:

# به‌جای import در بالای فایل
def my_function():
    from module_b import helper_function
    return helper_function()

UnboundLocalError: نوع خاصی از NameError

UnboundLocalError یک زیرکلاس از NameError است که وقتی رخ می‌دهد که در یک تابع، متغیری به‌عنوان local تعریف شده ولی قبل از اختصاص مقدار، خوانده شده است.

counter = 10

def increment():
    print(counter)  # UnboundLocalError
    counter = counter + 1

increment()

در این مثال، چون counter در تابع به‌عنوان local تعریف شده (به‌دلیل اختصاص مقدار در خط بعدی)، Python آن را local در نظر می‌گیرد. بنابراین print(counter) به‌جای خواندن مقدار گلوبال، به دنبال local می‌گردد که هنوز تعریف نشده.

راه‌حل‌ها:

راه اول: استفاده از global

counter = 10

def increment():
    global counter
    print(counter)
    counter = counter + 1

راه دوم: بازگرداندن مقدار

def increment(value):
    print(value)
    return value + 1

counter = 10
counter = increment(counter)

راه دوم اصولی‌تر است چون از side effect و تغییر گلوبال خودداری می‌کند.

global و nonlocal: دام‌های ظریف

کلمات کلیدی global و nonlocal در Python برای تغییر متغیرهای خارج از scope فعلی استفاده می‌شوند. عدم استفاده‌ی درست از این کلمات، منبع ظریف NameError و UnboundLocalError است.

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2

در این مثال، nonlocal باعث می‌شود که count در تابع بیرونی تغییر کند، نه یک متغیر local جدید در تابع داخلی.

نکته‌ی ظریف: nonlocal در Python 3 معرفی شد. در Python 2، این کلمه‌ی کلیدی وجود ندارد و باید از تکنیک‌های دیگر استفاده کرد.

# در Python 2 - تکنیک جایگزین
def outer():
    count = [0]  # استفاده از list برای mutability
    def inner():
        count[0] += 1
        return count[0]
    return inner

روش تشخیص اصولی در سه گام

در تجربه‌ی من، تشخیص NameError در چند ثانیه انجام می‌شود، اگر روش سیستماتیک داشته باشید:

گام اول: خواندن دقیق پیام خطا. پیام NameError دقیقاً می‌گوید کدام نام یافت نشد و در کدام خط. این دو داده، جهت جستجو را تعیین می‌کند.

گام دوم: جستجو در کد. با ابزار جستجوی ویرایشگر، نام یافت‌نشده را در کل پروژه جستجو کنید. اگر نام در جایی تعریف شده، بررسی کنید که آیا در scope درست است یا نه.

گام سوم: بررسی scope. اگر نام در یک تابع تعریف شده و شما از بیرون آن تابع استفاده می‌کنید، NameError خواهید گرفت. با بررسی ساختار تابع‌ها، این موضوع را تشخیص دهید.

ابزارهای تشخیص:

  • python -m py_compile script.py: کامپایل بدون اجرا، برای شناسایی SyntaxError.
  • pylint script.py: لینتر که NameErrorهای محتمل را تشخیص می‌دهد.
  • flake8 script.py: تشخیص خطاهای سبک و نام‌های نامعین.
  • mypy script.py: تحلیل نوع که بعضی از NameErrorها را تشخیص می‌دهد.
  • pyflakes script.py: ابزار سبک برای شناسایی نام‌های نامعین.

این ابزارها بخش بزرگی از NameErrorها را قبل از اجرا کشف می‌کنند.

راهبردهای رفع اصولی

بعد از تشخیص، نوبت به رفع می‌رسد. راهبردهای رفع، بر اساس نوع خطا متفاوت است:

راهبرد اول: صریح بودن در imports

به‌جای imports مبهم، از imports صریح استفاده کنید:

# اشتباه - مبهم
from os import *
from sys import *

# درست - صریح
import os
import sys
from datetime import datetime

این رویکرد، خوانایی و پایداری را افزایش می‌دهد.

راهبرد دوم: تعریف متغیرها قبل از استفاده

در توابع، متغیرها را در ابتدای تابع تعریف کنید:

def process_data():
    user_name = ""
    user_email = ""
    result = None
    
    user_name = get_user_name()
    # ...

راهبرد سوم: استفاده از type hints

Type hints در Python 3.5+ باعث می‌شود که ابزارهای تحلیل ایستا، نام‌های نامعین را زودتر تشخیص دهند:

def process_user(user_id: int) -> str:
    user_name: str = get_user_name(user_id)
    return user_name

راهبرد چهارم: مدیریت صریح خطا

در PHP 7+ و Python 3، می‌توانید این خطا را در try/except مدیریت کنید:

try:
    result = process_data()
except NameError as e:
    logging.error(f"Name error: {e}")
    result = None

نکته: NameError معمولاً یک باگ برنامه است، نه یک سناریوی طبیعی. بنابراین catch کردن آن به‌عنوان راه‌حل عمومی توصیه نمی‌شود.

راهبرد پنجم: بازنویسی ساختاری

در بعضی موارد، NameError نشانه‌ی مشکل ساختاری است. مثال: تابعی که به متغیر گلوبال وابسته است، می‌تواند بهتر باشد که پارامتر بگیرد:

# اشتباه - وابستگی به گلوبال
counter = 10
def increment():
    global counter
    counter += 1

# درست - پارامتر
def increment(value):
    return value + 1

counter = increment(counter)

مبانی مدیریت خطا در مدیریت خطا در پایتون آمده است.

ابزارهای پیشگیری و تحلیل ایستا

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

pylint: جامع‌ترین ابزار تحلیل ایستا. علاوه بر NameError، خطاهای سبک، پیچیدگی، و طراحی را تشخیص می‌دهد.

pylint script.py

flake8: ترکیبی از pycodestyle، pyflakes، و mccabe. سبک و سریع.

flake8 script.py

mypy: تحلیل نوع ایستا. با type hints، NameErrorهای مربوط به نوع را تشخیص می‌دهد.

mypy script.py

pyright: مشابه mypy، ولی سریع‌تر و با پشتیبانی بهتر از IDE.

ruff: ابزار مدرن که به‌جای pylint استفاده می‌شود. بسیار سریع‌تر از pylint و با قابلیت‌های مشابه.

ruff check script.py

در پروژه‌های CI/CD، ترکیب این ابزارها می‌تواند بخش بزرگی از NameErrorها را قبل از deployment کشف کند.

NameError در محیط production

در محیط production، NameError پیامدهای جدی‌تری دارد:

قطع کامل سرویس

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

نشت اطلاعات

پیام خطا، مسیر فایل و نام متغیر را افشا می‌کند. اگر این پیام به کاربر نمایش داده شود، اطلاعات حساس افشا می‌شود. راه‌حل: در production، لاگ‌ها را در جای امن نگه دارید و پیام‌های عمومی را به کاربر نشان دهید.

پایش و آلارم‌دهی

در production، NameError باید به‌طور فوری به تیم فنی اطلاع داده شود. ابزارهایی مثل Sentry، Rollbar، یا حتی لاگ‌های ساختارمند ساده، این پایش را فراهم می‌کنند.

پیشگیری با تست

بیشتر این خطاها را می‌توان قبل از production با تست‌های خودکار کشف کرد. تست‌های integration و unit test، بخش بزرگی از NameErrorها را در CI کشف می‌کنند. مبانی تست در مباحث Python آمده است.

اشتباهات رایج در برخورد با NameError

در طول سال‌ها، الگوهای تکراری از اشتباهات دیده‌ام که هر کدام می‌تواند پروژه را به چالش بکشد:

اشتباه اول: catch کردن عام NameError

اگر NameError را با try/except بلاک کنید، ممکن است باگ‌های واقعی را پنهان کنید. راه‌حل: NameError معمولاً یک باگ است، نه یک سناریوی طبیعی. آن را برطرف کنید، نه پنهان کنید.

اشتباه دوم: استفاده از global بی‌دلیل

اگر برای رفع NameError از global استفاده کنید، ممکن است باگ‌های ظریف‌تر ایجاد کنید. راه‌حل: از global فقط در موارد اجتناب‌ناپذیر استفاده کنید.

اشتباه سوم: نادیده گرفتن NameError در تست

اگر NameError را در تست‌ها نادیده بگیرید، در production ظاهر می‌شود. راه‌حل: در CI، تست‌ها را طوری تنظیم کنید که هر NameError باعث fail شوند.

اشتباه چهارم: نبود linting در CI

بدون linting در CI، هر commit می‌تواند NameErrorهای جدید وارد کند. راه‌حل: pylint یا flake8 را در CI قرار دهید.

اشتباه پنجم: نبود تست در staging

بدون تست در staging، خطاها به production می‌رسند. راه‌حل: همیشه تغییرات را در staging تست کنید.

اشتباه ششم: نادیده گرفتن این خطا در لاگ

اگر این خطا در لاگ نادیده گرفته شود، ممکن است هفته‌ها ادامه یابد بدون اینکه کسی متوجه شود. راه‌حل: پایش مداوم لاگ‌ها.

اشتباه هفتم: استفاده از exec و eval برای تعریف پویای نام

exec و eval می‌توانند نام‌ها را به‌طور پویا تعریف کنند، ولی این رویکرد هم امنیت و هم خوانایی را به خطر می‌اندازد. راه‌حل: به‌جای این ابزارها، از ساختارهای صریح استفاده کنید.

NameError یک سیگنال است، نه یک مشکل. این خطا می‌گوید: یک نام در یک لحظه‌ی خاص، در یک namespace اشتباه، تعریف شده. اگر این سیگنال را جدی بگیرید، ساختار کد شما هر روز بهتر می‌شود.

پرسش‌های پرتکرار درباره خطای NameError در پایتون

این پرسش‌ها از دل تجربه‌ی عملی و جلسات مشاوره جمع‌آوری شده‌اند. پاسخ هر کدام بر اساس سناریوهای واقعی است.

تفاوت NameError و UnboundLocalError چیست؟

UnboundLocalError یک زیرکلاس از NameError است که به‌طور خاص وقتی رخ می‌دهد که در یک تابع، متغیری به‌عنوان local در نظر گرفته شده ولی قبل از اختصاص مقدار خوانده شده. تفاوت: NameError عمومی است، UnboundLocalError خاص تابع.

چرا این خطا در زمان اجرا رخ می‌دهد نه در زمان کامپایل؟

در Python، NameError در زمان اجرا رخ می‌دهد، چون Python کد را خط‌به‌خط اجرا می‌کند. اگر بخشی از کد اجرا نشود، ممکن است NameError آن بخش هرگز ظاهر نشود. این تفاوت با IndentationError است که در زمان parse رخ می‌دهد.

آیا در Python 3، رفتار NameError تغییر کرده؟

خود رفتار NameError تغییری نکرده، ولی پیام‌های خطا در Python 3.x بهبود یافته‌اند. در Python 3.11 به بعد، پیام‌های خطا دقیق‌تر هستند و بیشتر شبیه به راهنمایی عمل می‌کنند.

چگونه از این خطا در پروژه‌های تیمی پیشگیری کنم؟

سه رویکرد: اول، استفاده از pylint یا flake8 در pre-commit hook. دوم، تنظیم CI که پروژه را با mypy یا pyright تحلیل می‌کند. سوم، تنظیمات یکسان ویرایشگر در تمام تیم با .editorconfig.

آیا می‌توانم از try/except برای NameError استفاده کنم؟

فنی بله، ولی توصیه نمی‌شود. NameError معمولاً یک باگ برنامه است، نه یک سناریوی طبیعی. catch کردن آن به‌عنوان راه‌حل عمومی، باگ‌های ظریف‌تری ایجاد می‌کند. در موارد خاص (مثل plugin systems)، می‌توانید از hasattr یا globals() برای بررسی وجود نام استفاده کنید.

چگونه بفهمم که یک نام در Python وجود دارد؟

با تابع globals() و locals():

if "my_variable" in globals():
    print(globals()["my_variable"])

یا با hasattr() برای کلاس‌ها و ماژول‌ها.

چرا بعد از copy-paste کد، NameError می‌گیرم؟

چون ممکن است در کد copy-paste شده، وابستگی‌هایی وجود داشته باشد که در کد مقصد وجود ندارد. راه‌حل: قبل از copy-paste، بررسی کنید که تمام نام‌های استفاده شده در کد مبدأ، در کد مقصد هم تعریف شده باشند.

چرا در Jupyter Notebook، NameError بیشتر دیده می‌شود؟

چون در Jupyter، سلول‌ها می‌توانند به ترتیب متفاوت اجرا شوند. اگر سلولی که متغیر را تعریف می‌کند، بعد از سلولی که از آن استفاده می‌کند، اجرا شود، NameError رخ می‌دهد. راه‌حل: اجرای سلول‌ها به ترتیب، یا استفاده از Restart & Run All.

آیا pylint همه‌ی NameErrorها را تشخیص می‌دهد؟

پیلینت بخش بزرگی از NameErrorها را تشخیص می‌دهد، ولی نه همه. بعضی NameErrorها به‌دلیل رفتار پویا (مثل exec یا __getattr__ سفارشی) از دید پیلینت پنهان می‌مانند. راه‌حل: ترکیب pylint با تست‌های خودکار.

آیا NameError روی performance تأثیر دارد؟

خود خطا در لحظه‌ی وقوع رخ می‌دهد. اگر مدیریت نشود و برنامه متوقف شود، performance تحت تأثیر است. اگر مدیریت شود، NameError از نظر performance هزینه‌ای ندارد چون در زمان اجرا فقط یک lookup در namespace است.

چگونه از این خطا در کدهای تودرتو (nested functions) پیشگیری کنم؟

در توابع تودرتو، از nonlocal استفاده کنید تا تغییر در متغیر تابع بیرونی انجام شود:

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
    inner()
    return count

آیا در Python 2 و 3، NameError یکسان است؟

رفتار کلی مشابه است، ولی تفاوت‌های ظریفی وجود دارد. در Python 2، بعضی نام‌ها به‌طور خودکار در namespace گلوبال قرار می‌گرفتند. در Python 3، این رفتار محدودتر شده و NameErrorهای بیشتری دیده می‌شود.

چگونه در pytest، NameErrorها را پوشش دهم؟

pytest به‌طور پیش‌فرض، NameErrorها را در تست‌ها گزارش می‌دهد و تست را fail می‌کند. برای اطمینان، در pytest می‌توانید از pytest.raises(NameError) استفاده کنید که تست را در صورت نبود NameError fail کند. الگوهای کامل در مباحث تست Python آمده است.

آیا NameError می‌تواند ناشی از import timeout باشد؟

غیرمستقیم، بله. اگر import یک ماژول با timeout مواجه شود و هیچ fallback ای در کد نباشد، ممکن است بخشی از کد که به آن ماژول وابسته است، NameError بدهد. راه‌حل: مدیریت خطا در importهای خارجی با try/except.

چرا در virtualenv، NameErrorهای بیشتری می‌بینم؟

چون در virtualenv، پکیج‌ها جدا هستند. اگر پکیجی در virtualenv نصب نباشد ولی در سیستم کلی باشد، در virtualenv NameError می‌گیرید. راه‌حل: فعال کردن virtualenv قبل از اجرا و نصب تمام وابستگی‌ها.

آیا type hints می‌تواند از NameError جلوگیری کند؟

غیرمستقیم بله. Type hints با ابزارهای تحلیل ایستا مثل mypy یا pyright، بخش بزرگی از NameErrorها را قبل از اجرا تشخیص می‌دهند. این رویکرد در پروژه‌های مدرن توصیه می‌شود.

چگونه از این خطا در فایل‌های Python که به‌عنوان script اجرا می‌شوند پیشگیری کنم؟

در فایل‌های script، مطمئن شوید که ترتیب تعریف‌ها درست است. متغیرها و توابعی که در سطح ماژول استفاده می‌شوند، باید قبل از استفاده تعریف شوند. اگر اسکریپت به‌عنوان ماژول هم استفاده می‌شود، از if __name__ == "__main__": استفاده کنید.

آیا NameError در asyncio رفتار متفاوتی دارد؟

در asyncio، رفتار NameError مشابه است، ولی خطاها ممکن است در event loop مدیریت شوند. اگر NameError در یک coroutine رخ دهد و مدیریت نشود، ممکن است به event loop منتقل شود و در جای غیرمنتظره ظاهر شود. راه‌حل: مدیریت صریح خطاها در coroutineها.

چرا در Cython یا Numba، NameError ممکن است متفاوت رفتار کند؟

در Cython و Numba، کد به زبان پایین‌تر کامپایل می‌شود. NameError ممکن است در زمان compile تشخیص داده شود، یا در زمان اجرا رفتار متفاوتی داشته باشد. راه‌حل: در این محیط‌ها، تنظیمات را با دقت بررسی کنید و از type hints استفاده کنید.

آنچه از سال‌ها کار با NameError در Python آموختم

اگر بخواهم چکیده‌ی این سال‌ها را در چند جمله بگویم، سه اصل عملی دارم:

یک: NameError یک تصمیم طراحی است، نه یک مشکل. Python عمداً نام‌های نامعین را رد می‌کند تا باگ‌ها را زودتر افشا کند. اگر این تصمیم را درونی کنید، هر NameError فرصتی برای بهبود کد است، نه یک شکست.

دو: ابزارهای تحلیل ایستا، سرمایه‌گذاری بلندمدت هستند. pylint، flake8، mypy، و مشابه‌ها، بخش بزرگی از NameErrorها را قبل از اجرا تشخیص می‌دهند. سرمایه‌گذاری روی این ابزارها، در بلندمدت چند برابر برمی‌گردد.

سه: انضباط در imports و scope، پایه‌ی کد مقاوم است. بخش بزرگی از NameErrorها از imports نادرست یا scope اشتباه می‌آید. اگر کد شما این دو را منظم داشته باشد، بخش بزرگی از NameErrorها پیشگیری می‌شود.

در کنار این سه اصل، یک هشدار عملی هم دارم: NameError در نگاه اول یک مشکل ساده به‌نظر می‌رسد، ولی وقتی در چارچوب کلی نگهداری پروژه دیده شود، تبدیل به یک سیگنال می‌شود. این سیگنال می‌گوید که ساختار پروژه‌ی شما نیاز به بازنگری دارد. اگر این سیگنال را جدی بگیرید و ساختار کد را بهبود دهید، پروژه‌ی شما در ماه‌های بعد تمیزتر، پایدارتر، و قابل نگهداری‌تر خواهد بود.

هدف این مقاله، تمام‌کردن همه‌ی سناریوهای ممکن نبود. هدف، دادن یک چارچوب ذهنی برای تشخیص، پیشگیری و رفع این خطا بود. وقتی این چارچوب را درونی کنید، برخورد با NameError از یک واکنش اضطراری به یک فرآیند منظم تبدیل می‌شود.

اگر NameError در پروژه‌ی شما به شکلی ظاهر شده که با الگوهای این مقاله حل نشده، برای من جالب است بدانم کدام سناریو بود. تجربه‌ی خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر راه‌حلی پیدا کرده‌اید که هنوز در این مقاله نیست. 🧩