__main__ --- Môi trường mã cấp cao nhất


Trong Python, tên đặc biệt __main__ được sử dụng cho hai cấu trúc quan trọng:

  1. tên của môi trường cấp cao nhất của chương trình, có thể được kiểm tra bằng biểu thức __name__ == '__main__'; và

  2. tệp __main__.py trong gói Python.

Cả hai cơ chế này đều liên quan đến các mô-đun Python; cách người dùng tương tác với họ và cách họ tương tác với nhau. Chúng được giải thích chi tiết dưới đây. Nếu bạn mới làm quen với các mô-đun Python, hãy xem phần hướng dẫn Mô-đun để biết phần giới thiệu.

__name__ == '__main__'

Khi mô-đun hoặc gói Python được nhập, __name__ được đặt thành tên của mô-đun. Thông thường, đây là tên của tệp Python không có phần mở rộng .py

>>> nhập trình phân tích cấu hình
>>> configparser.__name__
'trình phân tích cấu hình'

Nếu tệp là một phần của gói, __name__ cũng sẽ bao gồm đường dẫn của gói gốc:

>>> từ quá trình nhập concurrent.futures
>>> quá trình.__name__
'đồng thời.futures.process'

Tuy nhiên, nếu mô-đun được thực thi trong môi trường mã cấp cao nhất, __name__ của nó được đặt thành chuỗi '__main__'.

"Môi trường mã cấp cao nhất" là gì?

__main__ là tên của môi trường nơi mã cấp cao nhất được chạy. "Mã cấp cao nhất" là mô-đun Python do người dùng chỉ định đầu tiên bắt đầu chạy. Đó là "cấp cao nhất" vì nó nhập tất cả các mô-đun khác mà chương trình cần. Đôi khi "mã cấp cao nhất" được gọi là entry point đối với ứng dụng.

Môi trường mã cấp cao nhất có thể là:

  • phạm vi của lời nhắc tương tác:

    >>> __tên__
    '__chính__'
    
  • mô-đun Python được chuyển tới trình thông dịch Python dưới dạng đối số tệp:

    $ trăn helloworld.py
    Xin chào thế giới!
    
  • mô-đun hoặc gói Python được chuyển tới trình thông dịch Python với đối số -m:

    $ python -m tarfile
    cách sử dụng: tarfile.py [-h] [-v] (...)
    
  • Mã Python được trình thông dịch Python đọc từ đầu vào tiêu chuẩn:

    $ echo "nhập cái này" | trăn
    Thiền của Python, bởi Tim Peters
    
    Đẹp thì tốt hơn là xấu.
    Rõ ràng là tốt hơn ngầm.
    ...
    
  • Mã Python được chuyển tới trình thông dịch Python với đối số -c:

    $ python -c "nhập cái này"
    Thiền của Python, bởi Tim Peters
    
    Đẹp thì tốt hơn là xấu.
    Rõ ràng là tốt hơn ngầm.
    ...
    

Trong mỗi tình huống này, __name__ của mô-đun cấp cao nhất được đặt thành '__main__'.

Do đó, một mô-đun có thể phát hiện xem nó có đang chạy trong môi trường cấp cao nhất hay không bằng cách kiểm tra __name__ của chính nó, điều này cho phép một thành ngữ chung để thực thi mã có điều kiện khi mô-đun không được khởi tạo từ câu lệnh nhập:

nếu __name__ == '__main__':
    # Execute khi mô-đun không được khởi tạo từ câu lệnh nhập.
    ...

Xem thêm

Để có cái nhìn chi tiết hơn về cách cài đặt __name__ trong mọi tình huống, hãy xem phần hướng dẫn Mô-đun.

Cách sử dụng thành ngữ

Một số mô-đun chứa mã chỉ dành cho mục đích sử dụng tập lệnh, như phân tích cú pháp đối số dòng lệnh hoặc tìm nạp dữ liệu từ đầu vào tiêu chuẩn. Nếu một mô-đun như thế này được nhập từ một mô-đun khác, chẳng hạn như để kiểm tra đơn vị nó, mã tập lệnh cũng sẽ vô tình thực thi.

Đây là lúc việc sử dụng khối mã if __name__ == '__main__' trở nên hữu ích. Mã trong khối này sẽ không chạy trừ khi mô-đun được thực thi trong môi trường cấp cao nhất.

Việc đặt càng ít câu lệnh càng tốt trong khối bên dưới if __name__ == '__main__' có thể cải thiện độ rõ ràng và chính xác của mã. Thông thường, một hàm có tên main gói gọn hành vi chính của chương trình:

# echo.py

nhập khẩu shlex
hệ thống nhập khẩu

def echo(cụm từ: str) -> Không :
   """Một lớp bọc giả xung quanh bản in."""
   mục đích trình diễn # for, bạn có thể tưởng tượng rằng có một số
   # valuable và logic có thể tái sử dụng bên trong hàm này
   in (cụm từ)

def main() -> int:
    """Phản âm các đối số đầu vào thành đầu ra tiêu chuẩn"""
    cụm từ = shlex.join(sys.argv)
    tiếng vang (cụm từ)
    trở về 0

nếu __name__ == '__main__':
    Phần sys.exit(main()) # next giải thích cách sử dụng sys.exit

Lưu ý rằng nếu mô-đun không đóng gói mã bên trong hàm main mà thay vào đó đặt nó trực tiếp trong khối if __name__ == '__main__' thì biến phrase sẽ có tính chung cho toàn bộ mô-đun. Điều này dễ xảy ra lỗi vì các hàm khác trong mô-đun có thể vô tình sử dụng biến toàn cục thay vì tên cục bộ. Hàm main giải quyết vấn đề này.

Việc sử dụng hàm main còn có thêm lợi ích là bản thân hàm echo sẽ bị cô lập và có thể nhập ở nơi khác. Khi echo.py được nhập, các hàm echomain sẽ được xác định, nhưng cả hai hàm này sẽ không được gọi vì __name__ != '__main__'.

Cân nhắc về bao bì

Các hàm main thường được sử dụng để tạo các công cụ dòng lệnh bằng cách chỉ định chúng làm điểm vào cho các tập lệnh console. Khi việc này hoàn tất, pip sẽ chèn lệnh gọi hàm vào một tập lệnh mẫu, trong đó giá trị trả về của main được chuyển vào sys.exit(). Ví dụ:

sys.exit(chính())

Vì lệnh gọi tới main được gói trong sys.exit(), nên kỳ vọng là hàm của bạn sẽ trả về một số giá trị được chấp nhận làm đầu vào cho sys.exit(); thông thường, một số nguyên hoặc None (được trả về ngầm nếu hàm của bạn không có câu lệnh return).

Bằng cách chủ động tuân theo quy ước này, mô-đun của chúng tôi sẽ có hành vi tương tự khi chạy trực tiếp (tức là python echo.py) giống như sau này nếu chúng tôi đóng gói nó dưới dạng điểm nhập tập lệnh bảng điều khiển trong gói có thể cài đặt pip.

Đặc biệt, hãy cẩn thận khi trả về các chuỗi từ hàm main của bạn. sys.exit() sẽ hiểu đối số chuỗi là thông báo lỗi, do đó chương trình của bạn sẽ có mã thoát là 1, biểu thị lỗi và chuỗi sẽ được ghi vào sys.stderr. Ví dụ echo.py trước đó minh họa việc sử dụng quy ước sys.exit(main()).

Xem thêm

Python Packaging User Guide chứa tập hợp các hướng dẫn và tài liệu tham khảo về cách phân phối và cài đặt các gói Python bằng các công cụ hiện đại.

__main__.py trong gói Python

Nếu bạn không quen với các gói Python, hãy xem phần Gói của hướng dẫn. Thông thường nhất, tệp __main__.py được sử dụng để cung cấp giao diện dòng lệnh cho gói. Hãy xem xét gói giả định sau, "bandclass":

lớp nhạc
  ├── __init__.py
  ├── __main__.py
  └── sinh viên.py

__main__.py sẽ được thực thi khi chính gói đó được gọi trực tiếp từ dòng lệnh bằng cờ -m. Ví dụ:

$ python -m lớp băng

Lệnh này sẽ khiến __main__.py chạy. Cách bạn sử dụng cơ chế này sẽ phụ thuộc vào bản chất của gói bạn đang viết, nhưng trong trường hợp giả định này, có thể hợp lý khi cho phép giáo viên tìm kiếm học sinh:

# bandclass/__main__.py

hệ thống nhập khẩu
từ .student nhập search_students

sinh_name = sys.argv[1] if len(sys.argv) >= 2 else ''
print(f'Tìm thấy sinh viên: {search_students(student_name)}')

Lưu ý rằng from .student import search_students là một ví dụ về nhập tương đối. Kiểu nhập này có thể được sử dụng khi tham chiếu các mô-đun trong một gói. Để biết thêm chi tiết, hãy xem Tài liệu tham khảo trong gói trong phần Mô-đun của hướng dẫn.

Cách sử dụng thành ngữ

Nội dung của __main__.py thường không được rào bằng khối if __name__ == '__main__'. Thay vào đó, những tệp đó được giữ ngắn gọn và nhập các hàm để thực thi từ các mô-đun khác. Sau đó, các mô-đun khác có thể được kiểm thử đơn vị một cách dễ dàng và có thể tái sử dụng đúng cách.

Nếu được sử dụng, khối if __name__ == '__main__' sẽ vẫn hoạt động như mong đợi đối với tệp __main__.py trong một gói, vì thuộc tính __name__ của nó sẽ bao gồm đường dẫn của gói nếu được nhập:

>>> nhập asyncio.__main__
>>> asyncio.__main__.__name__
'asyncio.__main__'

Tuy nhiên, điều này sẽ không hoạt động đối với các tệp __main__.py trong thư mục gốc của tệp .zip. Do đó, để nhất quán, ưu tiên sử dụng __main__.py tối thiểu không có kiểm tra __name__.

Xem thêm

Xem venv để biết ví dụ về gói có __main__.py tối thiểu trong thư viện chuẩn. Nó không chứa khối if __name__ == '__main__'. Bạn có thể gọi nó bằng python -m venv [directory].

Xem runpy để biết thêm chi tiết về cờ -m cho trình thực thi trình thông dịch.

Xem zipapp để biết cách chạy các ứng dụng được đóng gói dưới dạng tệp .zip. Trong trường hợp này Python tìm tệp __main__.py trong thư mục gốc của kho lưu trữ.

import __main__

Bất kể chương trình Python được bắt đầu bằng mô-đun nào, các mô-đun khác chạy trong cùng chương trình đó có thể nhập phạm vi của môi trường cấp cao nhất (namespace) bằng cách nhập mô-đun __main__. Thao tác này không nhập tệp __main__.py mà nhập bất kỳ mô-đun nào nhận được tên đặc biệt '__main__'.

Đây là một mô-đun ví dụ sử dụng không gian tên __main__:

# namely.py

nhập __chính__

def did_user_define_your_name():
    trả về 'my_name' trong thư mục(__main__)

def print_user_name():
    nếu không did_user_define_their_name():
        raise ValueError('Xác định biến `my_name`!')

    in(__main__.my_name)

Ví dụ sử dụng mô-đun này có thể như sau:

# start.py

hệ thống nhập khẩu

từ cụ thể  nhập print_user_name

# my_name = "Dinsdale"

chắc chắn chính():
    thử:
        print_user_name()
    ngoại trừ ValueError như đã:
        trả về str(ve)

nếu __name__ == "__main__":
    sys.exit(chính())

Bây giờ, nếu chúng ta bắt đầu chương trình của mình, kết quả sẽ như thế này:

$ python start.py
Xác định biến `my_name`!

Mã thoát của chương trình sẽ là 1, báo lỗi. Bỏ ghi chú dòng có my_name = "Dinsdale" sẽ sửa chương trình và bây giờ nó thoát với mã trạng thái 0, biểu thị thành công:

$ python start.py
Dinsdale

Lưu ý rằng việc nhập __main__ không gây ra bất kỳ sự cố nào khi vô tình chạy mã cấp cao nhất dành cho việc sử dụng tập lệnh được đặt trong khối if __name__ == "__main__" của mô-đun start. Tại sao điều này làm việc?

Python chèn một mô-đun __main__ trống vào sys.modules khi khởi động trình thông dịch và điền nó bằng cách chạy mã cấp cao nhất. Trong ví dụ của chúng tôi, đây là mô-đun start chạy từng dòng và nhập namely. Đổi lại, namely nhập __main__ (thực sự là start). Đó là một chu kỳ nhập khẩu! May mắn thay, vì mô-đun __main__ được điền một phần có trong sys.modules, Python chuyển nó sang namely. Xem Special considerations for __main__ trong tài liệu tham khảo của hệ thống nhập để biết chi tiết về cách thức hoạt động của tính năng này.

Python REPL là một ví dụ khác về "môi trường cấp cao nhất", do đó, mọi thứ được xác định trong REPL đều trở thành một phần của phạm vi __main__:

>>> nhập khẩu cụ thể 
>>> cụ thể .did_user_define_their_name()
sai
>>> cụ thể .print_user_name()
Traceback (cuộc gọi gần đây nhất):
...
ValueError: Xác định biến `my_name`!
>>> my_name = 'Jabberwocky'
>>> cụ thể .did_user_define_their_name()
đúng
>>> cụ thể .print_user_name()
Jabberwocky

Phạm vi __main__ được sử dụng để triển khai pdbrlcompleter.