Dòng code khó hiểu nhất trong Python, giải thích cho rõ
Mở gần như bất kỳ tệp Python nào, sớm muộn bạn cũng sẽ thấy dòng này ở cuối:
if __name__ == "__main__":
main()
Trông nó như một đoạn code mẫu bí ẩn. Thật ra đây là một tính năng rất thực dụng khi bạn hiểu nó làm gì, và nó tồn tại vì Python không phân định rạch ròi giữa "script" và "thư viện".
Bối cảnh: hai cách dùng một tệp
Mọi tệp .py trong Python đều có thể được dùng theo hai cách:
- Như một script: bạn chạy trực tiếp bằng
python3 file.py. - Như một module: một tệp khác import nó bằng
import file.
Ngôn ngữ không bắt bạn khai báo mình đang viết loại nào. Tệp nào cũng có thể làm cả hai.
Xét một tệp tên math_helpers.py:
# math_helpers.py
def mean(values):
return sum(values) / len(values)
def median(values):
s = sorted(values)
mid = len(s) // 2
if len(s) % 2 == 0:
return (s[mid - 1] + s[mid]) / 2
return s[mid]
# Quick smoke test while developing
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("mean:", mean(numbers))
print("median:", median(numbers))
Nếu chạy trực tiếp tệp này, nó in ra kết quả thử nghiệm, rất tốt. Nhưng nếu một tệp khác viết import math_helpers, các câu lệnh print đó sẽ chạy ngay lúc import. Ai dùng module này cũng thấy đầu ra thử nghiệm lặt vặt của bạn. Không ổn chút nào.
__name__ là gì
Mọi module Python đều có một biến có sẵn tên là __name__. Python tự động đặt giá trị cho nó:
- Khi tệp được import,
__name__được đặt thành tên import của module, trong ví dụ trên là"math_helpers". - Khi tệp được chạy trực tiếp, Python đặt
__name__thành chuỗi đặc biệt"__main__".
Như vậy bạn có cách để biết mình đang ở chế độ nào. Bọc phần code "chỉ dành cho script" trong một phép kiểm tra:
# math_helpers.py
def mean(values):
return sum(values) / len(values)
def median(values):
s = sorted(values)
mid = len(s) // 2
if len(s) % 2 == 0:
return (s[mid - 1] + s[mid]) / 2
return s[mid]
if __name__ == "__main__":
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("mean:", mean(numbers))
print("median:", median(numbers))
Giờ python3 math_helpers.py in ra kết quả thử nghiệm. Còn import math_helpers từ một tệp khác thì bỏ qua hoàn toàn khối này: các hàm vẫn dùng được, và không có gì ngoài ý muốn bị chạy.
Người bạn đồng hành quen thuộc: hàm main()
Phần lớn script thực tế sẽ có một hàm main() riêng và giữ khối guard thật ngắn:
# process_orders.py
import sys
def load_orders(path):
with open(path) as f:
return [line.strip() for line in f if line.strip()]
def process(orders):
for order in orders:
print(f"Processing {order}")
def main():
if len(sys.argv) < 2:
print("Usage: python3 process_orders.py <file>")
sys.exit(1)
orders = load_orders(sys.argv[1])
process(orders)
if __name__ == "__main__":
main()
Vì sao phải viết main() thay vì đặt logic ngay dưới guard if?
- Dễ kiểm thử. Bạn có thể import module và gọi
main()(hoặc bất kỳ hàm phụ trợ nào) từ test. - Biến cục bộ. Mọi thứ định nghĩa trong
main()đều là biến cục bộ, không phải tên ở cấp module. Điều đó tránh được xung đột tên ngoài ý muốn và giúp phần công khai của module gọn gàng hơn. - Dễ đọc. Khối guard giờ chỉ còn hai dòng, một "điểm bắt đầu của script" rõ ràng thay vì một đống logic.
Minh họa nhanh
Bạn có thể tự thấy cách nó hoạt động:
Khi chạy đoạn code trên bằng Python, nó in ra __name__ is: __main__ và "being run as a script." Nếu một tệp khác import tệp này, cùng phép kiểm tra đó sẽ in ra nhánh còn lại.
Khi nào không cần guard
Rất nhiều tệp Python không bao giờ cần đến nó. Những script ngắn mà chẳng ai import thì không có guard cũng hoàn toàn ổn:
# one_off_rename.py
import os
for name in os.listdir("."):
if name.endswith(".txt"):
os.rename(name, name.lower())
Như vậy là hoàn toàn tốt. Bạn không xây dựng thư viện, và chẳng ai sẽ import tệp này. Nhưng ngay khi tệp có logic dùng lại được, mà bạn vẫn muốn giữ phần code có tác dụng phụ bên cạnh, hãy dùng guard.
Điểm bắt đầu cho package đã cài đặt
Khi bạn phát hành code thành một package thực sự, có một cơ chế chính thức hơn: entry_points trong metadata của package, ánh xạ một lệnh tới một hàm. Người cài package của bạn sẽ có một lệnh terminal gọi thẳng hàm đó. Còn với script chạy từ một thư mục trên máy của bạn, guard __main__ là công cụ chuẩn.
Tổng kết chương
Giờ bạn đã có:
- Hàm để đặt tên cho logic.
*args/**kwargscho những lời gọi linh hoạt.- Lambda cho các hàm dùng một lần.
- Decorator và type hint để bọc và ghi chú cho các hàm đó.
- Module, pip và môi trường ảo để chia code ra nhiều tệp và dự án.
- Guard
__main__để script và module cùng tồn tại.
Tiếp theo: lớp
Đến giờ bạn chủ yếu ghép hàm với dữ liệu thuần: dict, list, tuple. Chương tiếp theo giới thiệu lớp (class), cách Python gắn dữ liệu với hành vi, và cũng là nơi có những khái niệm như self, __init__, kế thừa và dataclass.
Câu hỏi thường gặp
if __name__ == '__main__' làm gì?
Nó chỉ chạy khối lệnh bên dưới khi tệp được thực thi trực tiếp (không chạy khi tệp được import như một module). Nhờ vậy bạn có thể viết một tệp .py vừa chạy được như script, vừa import được để dùng các hàm và lớp bên trong, mà phần việc của script không bị chạy lúc import.
Python có cần hàm main không?
Không. Tệp Python tự động chạy từ trên xuống dưới. Nhưng ngay khi một tệp có logic mà bạn cũng muốn import được, guard if __name__ == '__main__' trở nên quan trọng, và đặt phần việc chính của script vào trong một hàm main() là thói quen thường đi kèm.
__name__ và __main__ khác nhau thế nào?
__name__ là một biến đặc biệt mà Python đặt trong mọi module. Nếu tệp được import, __name__ là tên của module (như 'util'). Nếu tệp được chạy trực tiếp, Python đặt __name__ thành chuỗi '__main__'. Câu lệnh if so sánh hai giá trị này.