Khi bạn không biết sẽ có bao nhiêu đối số
Phần lớn các hàm (function) nhận một số lượng tham số cố định. Nhưng đôi khi bạn cần thứ linh hoạt hơn: một hàm ghi log nhận bao nhiêu thông điệp cũng được, một hàm bọc chuyển tiếp mọi thứ nó nhận được sang hàm khác, hay một hàm vẽ biểu đồ nhận cấu hình mà không biết trước người gọi sẽ dùng những tùy chọn nào.
Python xử lý chuyện này bằng hai ký hiệu đặc biệt trong danh sách tham số: *args và **kwargs.
*args gom các đối số vị trí
Một dấu sao có nghĩa là "gom mọi đối số vị trí thừa vào một tuple":
Bên trong hàm, args là một tuple bình thường. Bạn có thể lặp qua nó, truy cập theo chỉ số, truyền nó vào len() hoặc cắt lát (slice) nó.
*args thường đi cùng các tham số có tên:
first lấy đối số đầu tiên; *rest gom mọi thứ phía sau vào một tuple.
**kwargs gom các đối số từ khóa
Hai dấu sao làm việc tương tự cho đối số từ khóa (keyword argument), gom chúng vào một dict:
Bên trong describe, kwargs là một dict bình thường. Các khóa là tên của từ khóa, ở dạng chuỗi.
Dùng cả hai cùng lúc
Bạn có thể dùng cả hai trong cùng một hàm. Theo quy ước, *args đứng trước **kwargs:
Thứ tự đầy đủ của tham số, từ trái sang phải:
- Các tham số vị trí thông thường (bắt buộc hoặc có giá trị mặc định).
*args.- Các tham số chỉ nhận từ khóa (mọi thứ sau
*argsphải được truyền bằng từ khóa). **kwargs.
title lấy đối số vị trí đầu tiên. Các đối số vị trí còn lại đi vào tags. draft phải được truyền bằng từ khóa (vì nó đứng sau *tags). Mọi đối số từ khóa khác rơi vào metadata.
Giải nén bằng * và ** khi gọi hàm
Dấu sao cũng hoạt động theo chiều ngược lại: trải một dãy hoặc một dict ra thành các đối số của lời gọi hàm:
Cách này cực kỳ tiện để chuyển tiếp đối số:
wrapped không cần biết log cần những đối số nào. Nó chỉ gom tất cả rồi chuyển tiếp nguyên vẹn. Mẫu này xuất hiện liên tục trong decorator (một chủ đề nâng cao hơn) và các hàm bọc.
Khi *args và **kwargs là lựa chọn sai
Rất dễ hào hứng quá mức rồi dùng chúng ở khắp nơi. Có hai điều cần cảnh giác:
Chúng che giấu những gì hàm cần
Nếu mọi hàm trong codebase của bạn đều là def f(*args, **kwargs), code gọi hàm sẽ không biết đối số nào hợp lệ. Hãy dùng tham số có tên bất cứ khi nào có thể, và chỉ để *args/**kwargs mang những đầu vào thật sự có số lượng thay đổi, hoặc chỉ để chuyển tiếp.
Thông báo lỗi trở nên mơ hồ
Một tên từ khóa gõ nhầm sẽ thành một None lặng lẽ hoặc một KeyError nằm sâu trong hàm, thay vì báo ngay "unexpected keyword argument" tại chỗ gọi. Tham số có tên cho bạn phản hồi tốt hơn nhiều.
Quy tắc chung: mặc định hãy dùng tham số có tên, và chỉ dùng tới *args/**kwargs khi hàm thật sự cần linh hoạt hoặc đang chuyển tiếp đối số sang một đối tượng gọi được (callable) khác.
Một ví dụ thực tế nhỏ
Một hàm hỗ trợ kiểu hàm vẽ biểu đồ, bọc một hàm của bên thứ ba với vài giá trị mặc định:
*values cho phép người gọi truyền bao nhiêu phần tử cũng được; **style nhận cấu hình bổ sung mà không bắt mọi tùy chọn phải là một tham số có tên. Hàm này linh hoạt mà không khó hiểu, vì logic bên trong cho bạn thấy chính xác nó đọc những khóa nào từ style.
Tóm tắt
*argsgom các đối số vị trí thừa vào một tuple.**kwargsgom các đối số từ khóa thừa vào một dict.- Khi gọi hàm,
*seqvà**dictgiải nén theo chiều ngược lại. - Tham số phải xuất hiện theo thứ tự: thông thường →
*args→ chỉ nhận từ khóa →**kwargs. - Đừng lạm dụng: tham số có tên rõ ràng hơn khi bạn dùng được.
Tiếp theo: lambda, cách viết những hàm nhỏ dùng một lần ngay tại chỗ.
Câu hỏi thường gặp
*args trong Python là gì?
*args gom mọi đối số vị trí thừa vào một tuple. Với def f(*args):, bạn có thể gọi f(1, 2, 3) và nhận args là (1, 2, 3). Tên args chỉ là quy ước: bạn đặt tên gì cũng được, nhưng ai cũng dùng *args.
**kwargs trong Python là gì?
**kwargs gom mọi đối số từ khóa thừa vào một dict. Với def f(**kwargs):, bạn có thể gọi f(name='Ada', age=30) và nhận kwargs là {'name': 'Ada', 'age': 30}. Kết hợp lại, *args và **kwargs cho phép một hàm nhận bất kỳ tổ hợp đối số nào.
Có bắt buộc phải đặt tên là args và kwargs không?
Không, dấu sao mới là thứ quan trọng, không phải cái tên. *values và **options hoạt động y hệt. Nhưng args và kwargs là quy ước gần như phổ quát trong code Python, nên cứ dùng chúng, trừ khi có lý do cụ thể để chọn một tên mô tả rõ hơn.