Câu 1Code này in ra gì?def f(*args):
return sum(args)
print(f(1, 2, 3, 4))
Đối số có độ dài thay đổi *args / **kwargs và nhiều giá trị trả về
Học cách dùng args và kwargs trong Python để nhận đối số có độ dài thay đổi và trả về nhiều giá trị từ một hàm.
Trong bài trước về def, bạn đã định nghĩa hàm với số đối số cố định. Tuy nhiên trong code thực tế, số đối số không phải lúc nào cũng biết trước, và đôi khi bạn muốn trả về nhiều hơn một kết quả từ một lần gọi.
Python xử lý những tình huống này với đối số có độ dài thay đổi và nhiều giá trị trả về. Cả hai đều được xây dựng trực tiếp trên những gì bạn đã biết về tuple và dict.
Đối số vị trí có độ dài thay đổi *args — gom thành một tuple
Đặt một dấu sao trước tên đối số (*args) và hàm có thể nhận bất kỳ số lượng đối số vị trí nào. Bên trong hàm, các đối số đó có sẵn dưới dạng một tuple.
Tên args là quy ước, nhưng về mặt kỹ thuật bất kỳ tên mô tả nào cũng được — *prices, *messages, miễn sao đọc hợp lý cho trường hợp sử dụng.
# Nhận bất kỳ số lượng đối số vị trí nào
def sum_all(*prices):
print(prices) # (100, 200, 300)
print(type(prices)) # <class 'tuple'>
return sum(prices)
print(sum_all(100, 200, 300)) # 600
print(sum_all(500)) # 500 (một cũng được)
print(sum_all()) # 0 (không có thì cho tuple rỗng)
Kết hợp đối số thông thường với *args
Đặt đối số thông thường trước *args và bạn có thể trộn một đối số đầu cố định với một phần đuôi có độ dài thay đổi. Viết log(level, *messages) nghĩa là giá trị đầu tiên được gán cho level và phần còn lại được gom vào một tuple tên messages. Thứ tự là đối số thông thường → *args.
def log(level, *messages):
for m in messages:
print(f"[{level}] {m}")
log("INFO", "Đã khởi động", "Đã kết nối")
# [INFO] Đã khởi động
# [INFO] Đã kết nối
Đối số từ khóa có độ dài thay đổi **kwargs — gom thành một dict
Hai dấu sao (`kwargs) cho phép hàm nhận bất kỳ đối số từ khóa nào ở dạng name=value. Bên trong hàm, các đối số đó đến dưới dạng một dict, điều này khiến nó hoàn hảo khi các key chưa biết trước**.
Tên theo quy ước là kwargs (viết tắt của keyword arguments).
Gọi show(name="Minh", age=30) sẽ cho bên nhận dict {"name": "Minh", "age": 30}. Việc không phải khai báo các key trước chính là cốt lõi của nó.
def describe_user(**info):
print(info) # {'name': 'Minh', 'age': 30, 'city': 'Hà Nội'}
print(type(info)) # <class 'dict'>
for key, value in info.items():
print(f"{key}: {value}")
describe_user(name="Minh", age=30, city="Hà Nội")
Kết hợp đối số thông thường với **kwargs
Đặt một đối số thông thường trước **kwargs và bạn có **một giá trị bắt buộc cố định** cộng với **các trường tùy chọn linh hoạt**. Định nghĩa def save_user(name, **info): nghĩa là name luôn bắt buộc, trong khi các trường thêm như age=30 hoặc city="Hà Nội" sẽ được gom vào dict info.
def save_user(name, **info):
print(f"name: {name}")
for key, value in info.items():
print(f"{key}: {value}")
save_user("Minh", age=30, city="Hà Nội")
# name: Minh
# age: 30
# city: Hà Nội
Dùng đối số thông thường, *args, và **kwargs cùng nhau
Cả ba loại đối số có thể cùng tồn tại trong một hàm. Thứ tự cố định: đối số thông thường → *args → **kwargs. Viết def register(user_id, *tags, **meta): sẽ gán giá trị đầu tiên cho user_id, gom mọi đối số vị trí tiếp theo vào tuple tags, và gom mọi đối số key=value vào dict meta.
def register(user_id, *tags, **meta):
print(f"id: {user_id}")
print(f"tags: {tags}")
print(f"meta: {meta}")
register(1001, "admin", "beta", email="minh@example.com", active=True)
# id: 1001
# tags: ('admin', 'beta')
# meta: {'email': 'minh@example.com', 'active': True}
Unpacking ở phía gọi — *list / **dict
Khi truyền một list hoặc dict đã có sẵn cho một hàm, việc có hay không đặt * hoặc ** phía trước sẽ thay đổi kết quả đáng kể.
Nếu truyền nguyên xi không có * hoặc **, list hoặc dict được coi **như một giá trị duy nhất không được unpack**. Nhận nó qua *args và bạn sẽ có một tuple chứa một list (ví dụ ([1, 2, 3],)`) — đó là cái bẫy.
Với `list / dict ở phía gọi, nội dung được trải ra từng cái một, nên *args / **kwargs thấy đúng tuple (1, 2, 3) hoặc dict {"name": "Minh"} như mong đợi.
def sum_all(*prices):
return sum(prices)
prices = [120, 280, 550]
# X: truyền như một list duy nhất
# print(sum_all(prices)) # lỗi (sum cố cộng list với số)
# O: trải ra với `*`
print(sum_all(*prices)) # 950 — giống sum_all(120, 280, 550)
# Cùng ý tưởng với dict
def announce(name, city):
print(f"{name} đến từ {city}")
user = {"name": "Linh", "city": "Đà Nẵng"}
announce(**user) # giống announce(name="Linh", city="Đà Nẵng")
Unpacking tỏa sáng khi truyền dữ liệu có sẵn
Khi bạn đã có dữ liệu dưới dạng list hoặc dict, unpacking đưa nó vào hàm chỉ trong một dòng.
Ví dụ, dict user = {"name": "Linh", "city": "Đà Nẵng"} trở thành announce(**user) — giống hệt như viết announce(name="Linh", city="Đà Nẵng"). Bạn không cần lấy từng key ra bằng tay.
Nhiều giá trị trả về — trả về dưới dạng tuple, unpack khi nhận
return chấp nhận nhiều giá trị cách nhau bởi dấu phẩy, và bên gọi nhận một tuple của các giá trị đó. Nhận chúng với a, b = function() để unpack tuple thành nhiều biến cùng lúc.
Điều này rất tiện khi bạn muốn trả về nhiều giá trị từ một hàm (min và max, username và role, cờ thành công và thông điệp, v.v.).
# Trả về nhiều giá trị cùng lúc
def get_person():
return "Minh", 30, "Hà Nội"
# Nhận dưới dạng tuple
result = get_person()
print(result) # ('Minh', 30, 'Hà Nội')
print(type(result)) # <class 'tuple'>
# Hoặc unpack thành các biến riêng
name, age, city = get_person()
print(name, age, city) # Minh 30 Hà Nội
# Ví dụ thực tế: trả về min và max cùng nhau
def get_min_max(values):
return min(values), max(values)
lowest, highest = get_min_max([120, 500, 300, 180])
print(f"min {lowest} / max {highest}")
Dùng *rest để vớ phần còn lại
Cho một list điểm thi như return 90, 85, 78, 92, 88, viết first, *middle, last = ... và bạn sẽ có first=90, last=88, middle=[85, 78, 92] — giá trị đầu và cuối, cộng phần còn lại được gom thành một list. Đây là công cụ phù hợp khi bạn muốn giữ phần đầu và đuôi và gom hết những thứ ở giữa.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Kiểu của kwargs được nhận bởi def f(**kwargs): là gì?
Câu 3Cho nums = [1, 2, 3], lời gọi nào truyền nội dung của nó dưới dạng ba đối số vị trí cho f(a, b, c)?