__main__ --- محیط سطح‌بالای کد


در پایتون، از نام ویژه‌ی __main__ برای دو ساختار مهم استفاده می‌شود:

  1. نام محیط سطح بالای برنامه، که می‌توان آن را با استفاده از عبارت __name__ == '__main__' بررسی کرد؛ و

  2. پرونده __main__.py در بسته‌های پایتون.

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

__name__ == '__main__'

هنگامی که یک ماژول یا بسته‌ی پایتون ایمپورت می‌شود، __name__ به نام ماژول تنظیم می‌شود. معمولاً این همان نام پرونده پایتون است بدون پسوند .py:

>>> import configparser
>>> configparser.__name__
'configparser'

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

>>> from concurrent.futures import process
>>> process.__name__
'concurrent.futures.process'

با این حال، اگر ماژول در محیط کد سطح بالا اجرا شود، __name__ آن به رشته‌ی '__main__' تنظیم می‌شود.

«محیط کد سطح‌بالا» چیست؟

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

محیط سطح‌بالای کد می‌تواند باشد:

  • محدوده‌ی یک دستور تعاملی:

    >>> __name__
    '__main__'
    
  • ماژول پایتونی که به‌عنوان آرگومان فایل به مفسر پایتون پاس داده می‌شود:

    $ python helloworld.py
    Hello, world!
    
  • ماژول یا بسته‌ی پایتونی که با آرگومان -m به مفسر پایتون پاس داده می‌شود:

    $ python -m tarfile
    usage: tarfile.py [-h] [-v] (...)
    
  • کد پایتون توسط مفسر پایتون از ورودی استاندارد خوانده می‌شود:

    $ echo "import this" | python
    The Zen of Python, by Tim Peters
    
    Beautiful is better than ugly.
    Explicit is better than implicit.
    ...
    
  • کد پایتون با آرگومان -c به مفسر پایتون پاس داده می‌شود:

    $ python -c "import this"
    The Zen of Python, by Tim Peters
    
    Beautiful is better than ugly.
    Explicit is better than implicit.
    ...
    

در هر کدام از این موقعیت‌ها، __name__ ماژول سطح‌بالا به '__main__' تنظیم می‌شود.

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

if __name__ == '__main__':
    # وقتی ماژول با دستور ایمپورت راه‌اندازی نشده باشد اجرا می‌شود.
    ...

همچنین ملاحظه نمائید

برای نگاهی دقیق‌تر به نحوه‌ی تنظیم __name__ در همه‌ی موقعیت‌ها، به بخش آموزش ماژول‌ها مراجعه کنید.

استفاده‌ی اصطلاحی

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

اینجاست که استفاده از بلوک کد if __name__ == '__main__' به کار می‌آید. کد داخل این بلوک اجرا نمی‌شود مگر اینکه ماژول در محیط سطح‌بالا اجرا شود.

قرار دادن کمترین تعداد دستور ممکن در بلوک زیر if __name__ == '__main__' می‌تواند وضوح و صحت کد را بهبود بخشد. اغلب، یک تابع به نام main رفتار اصلی برنامه را در خود محصور می‌کند:

# echo.py

import shlex
import sys

def echo(phrase: str) -> None:
   """یک پوشش ساختگی برای print."""
   # برای اهداف نمایشی، می‌توانید تصور کنید که منطق ارزشمندی
   # و قابل استفاده‌ی مجددی درون این تابع وجود دارد
   print(phrase)

def main() -> int:
    """آرگومان‌های ورودی را به خروجی استاندارد بازتاب می‌دهد"""
    phrase = shlex.join(sys.argv)
    echo(phrase)
    return 0

if __name__ == '__main__':
    sys.exit(main())  # بخش بعدی استفاده از sys.exit را توضیح می‌دهد

توجه داشته باشید که اگر ماژول کد را درون تابع main محصور نمی‌کرد و به جای آن مستقیماً آن را درون بلوک if __name__ == '__main__' قرار می‌داد، متغیر phrase برای کل ماژول سراسری می‌شد. این امر مستعد خطا است، زیرا ممکن است سایر توابع درون ماژول به‌طور ناخواسته از متغیر سراسری به جای یک نام محلی استفاده کنند. تابع main این مشکل را حل می‌کند.

استفاده از تابع main این مزیت اضافی را دارد که خود تابع echo ایزوله و قابل ایمپورت در جای دیگر است. وقتی echo.py ایمپورت می‌شود، توابع echo و main تعریف می‌شوند، اما هیچ‌کدام از آن‌ها فراخوانی نمی‌شوند، زیرا __name__ != '__main__' است.

ملاحظات بسته‌بندی

توابع main اغلب برای ساختن ابزارهای خط فرمان با مشخص کردن آن‌ها به‌عنوان نقاط ورودی برای اسکریپت‌های کنسولی استفاده می‌شوند. وقتی این کار انجام می‌شود، pip فراخوانی تابع را در یک اسکریپت الگو قرار می‌دهد، جایی که مقدار بازگشتی main به sys.exit() پاس داده می‌شود. برای مثال:

sys.exit(main())

از آنجایی که فراخوانی main در sys.exit() پیچیده شده است، انتظار می‌رود که تابع شما مقداری را برگرداند که به‌عنوان ورودی برای sys.exit() قابل‌قبول باشد؛ معمولاً یک عدد صحیح یا None (که اگر تابع شما دستور return نداشته باشد، به‌صورت ضمنی برگردانده می‌شود).

با پیروی فعالانه‌ی خودمان از این قرارداد، ماژول ما هنگام اجرای مستقیم (یعنی python echo.py) همان رفتاری را خواهد داشت که در صورت بسته‌بندی بعدی آن به‌عنوان یک نقطه‌ی ورودی اسکریپت کنسول در یک بسته‌ی قابل نصب با pip خواهد داشت.

به‌ویژه، در مورد بازگرداندن رشته‌ها از تابع main خود دقت کنید. sys.exit() یک آرگومان رشته‌ای را به‌عنوان پیام شکست تفسیر می‌کند، بنابراین برنامه‌ی شما کد خروجی 1 را خواهد داشت که نشان‌دهنده‌ی شکست است و رشته به sys.stderr نوشته می‌شود. مثال echo.py از قبل، استفاده از قرارداد sys.exit(main()) را به‌خوبی نشان می‌دهد.

همچنین ملاحظه نمائید

Python Packaging User Guide شامل مجموعه‌ای از آموزش‌ها و مراجع درباره‌ی نحوه‌ی توزیع و نصب بسته‌های پایتون با ابزارهای مدرن است.

__main__.py در بسته‌های پایتون

اگر با بسته‌های پایتون آشنایی ندارید، به بخش بسته‌ها از آموزش مراجعه کنید. معمولاً، از فایل __main__.py برای ارائه‌ی یک رابط خط فرمان برای یک بسته استفاده می‌شود. بسته‌ی فرضی زیر را در نظر بگیرید، "bandclass":

bandclass
  ├── __init__.py
  ├── __main__.py
  └── student.py

__main__.py زمانی اجرا می‌شود که خود بسته مستقیماً از خط فرمان با استفاده از پرچم -m فراخوانی شود. برای مثال:

$ python -m bandclass

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

# bandclass/__main__.py

import sys
from .student import search_students

student_name = sys.argv[1] if len(sys.argv) >= 2 else ''
print(f'Found student: {search_students(student_name)}')

توجه داشته باشید که from .student import search_students مثالی از یک ایمپورت نسبی است. این سبک ایمپورت را می‌توان هنگام ارجاع به ماژول‌های درون یک بسته استفاده کرد. برای اطلاعات بیشتر، به ارجاع‌های درون‌بسته‌ای در بخش ماژول‌ها از آموزش مراجعه کنید.

استفاده‌ی اصطلاحی

محتوای __main__.py معمولاً با یک بلوک if __name__ == '__main__' محصور نمی‌شود. در عوض، آن فایل‌ها کوتاه نگه داشته می‌شوند و توابعی را برای اجرا از ماژول‌های دیگر ایمپورت می‌کنند. سپس آن ماژول‌های دیگر را می‌توان به‌راحتی یونیت‌تست کرد و به‌درستی قابل استفاده‌ی مجدد هستند.

اگر استفاده شود، یک بلوک if __name__ == '__main__' همچنان برای یک فایل __main__.py درون یک بسته به‌طور مورد انتظار کار خواهد کرد، زیرا ویژگی __name__ آن در صورت ایمپورت، مسیر بسته را شامل خواهد شد:

>>> import asyncio.__main__
>>> asyncio.__main__.__name__
'asyncio.__main__'

با این حال، این برای فایل‌های __main__.py در پوشه‌ی ریشه‌ی یک فایل .zip کار نخواهد کرد. بنابراین، برای یکدستی، یک __main__.py حداقلی بدون بررسی __name__ ترجیح داده می‌شود.

همچنین ملاحظه نمائید

برای مثال یک بسته با یک __main__.py حداقلی در کتابخانه‌ی استاندارد، به venv مراجعه کنید. این بسته شامل یک بلوک if __name__ == '__main__' نمی‌شود. می‌توانید آن را با python -m venv [directory] فراخوانی کنید.

برای جزئیات بیشتر درباره‌ی پرچم -m به مفسر اجرایی، به runpy مراجعه کنید.

برای نحوه‌ی اجرای برنامه‌های بسته‌بندی‌شده به‌عنوان فایل‌های .zip، به zipapp مراجعه کنید. در این حالت، پایتون به دنبال یک فایل __main__.py در پوشه‌ی ریشه‌ی بایگانی می‌گردد.

import __main__

صرف‌نظر از اینکه یک برنامه‌ی پایتون با کدام ماژول شروع شده است، سایر ماژول‌هایی که در همان برنامه اجرا می‌شوند می‌توانند با ایمپورت کردن ماژول __main__، به محدوده‌ی محیط سطح‌بالا (namespace) دسترسی پیدا کنند. این کار یک فایل __main__.py را ایمپورت نمی‌کند، بلکه هر ماژولی که نام ویژه‌ی '__main__' را دریافت کرده است، ایمپورت می‌شود.

در اینجا یک مثال از ماژول آورده شده است که از فضای نام __main__ استفاده می‌کند:

# namely.py

import __main__

def did_user_define_their_name():
    return 'my_name' in dir(__main__)

def print_user_name():
    if not did_user_define_their_name():
        raise ValueError('Define the variable `my_name`!')

    print(__main__.my_name)

یک نمونه استفاده از این ماژول می‌تواند به صورت زیر باشد:

# start.py

import sys

from namely import print_user_name

# my_name = "Dinsdale"

def main():
    try:
        print_user_name()
    except ValueError as ve:
        return str(ve)

if __name__ == "__main__":
    sys.exit(main())

اکنون، اگر ما برنامه‌مان را شروع کنیم، نتیجه مثل زیر می‌شود:

$ python start.py
Define the variable `my_name`!

کد خروجی برنامه ۱ خواهد بود که نشان‌دهنده‌ی خطا است. خارج کردن خط my_name = "Dinsdale" از حالت توضیح (کامنت)، برنامه را اصلاح می‌کند و اکنون با کد وضعیت ۰ خارج می‌شود که نشان‌دهنده‌ی موفقیت است:

$ python start.py
Dinsdale

توجه داشته باشید که ایمپورت کردن __main__ هیچ مشکلی در اجرای ناخواسته‌ی کد سطح‌بالا که برای استفاده‌ی اسکریپتی در نظر گرفته شده و در بلوک if __name__ == "__main__" ماژول start قرار دارد، ایجاد نمی‌کند. چرا این کار می‌کند؟

پایتون در زمان راه‌اندازی مفسر، یک ماژول خالی __main__ را در sys.modules قرار می‌دهد و آن را با اجرای کد سطح‌بالا پر می‌کند. در مثال ما، این ماژول start است که خط به خط اجرا می‌شود و namely را ایمپورت می‌کند. در ادامه، namely ماژول __main__ (که در واقع همان start است) را ایمپورت می‌کند. این یک چرخه‌ی ایمپورت است! خوشبختانه، از آنجایی که ماژول __main__ تا حدی پر شده در sys.modules وجود دارد، پایتون آن را به namely می‌دهد. برای جزئیات بیشتر درباره‌ی نحوه‌ی کار این مکانیزم، به Special considerations for __main__ در مرجع سیستم ایمپورت مراجعه کنید.

REPL پایتون مثال دیگری از یک «محیط سطح‌بالا» است، بنابراین هر چیزی که در REPL تعریف شود، بخشی از محدوده‌ی __main__ می‌شود:

>>> import namely
>>> namely.did_user_define_their_name()
False
>>> namely.print_user_name()
Traceback (most recent call last):
...
ValueError: Define the variable `my_name`!
>>> my_name = 'Jabberwocky'
>>> namely.did_user_define_their_name()
True
>>> namely.print_user_name()
Jabberwocky

محدوده‌ی __main__ در پیاده‌سازی pdb و rlcompleter استفاده می‌شود.