Học bằng cách đọc theo thứ tự

Đố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 đổinhiề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.

Gom đối số vị trí với *args
phía gọisum_all(100, 200, 300)định nghĩadef sum_all(*prices):bên trong hàmprices = (100, 200, 300)truyềngom
# 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.

Đối số thông thường kết hợp với *args
log("INFO", "Đã khởi động", "Đã kết nối")def log(level, *messages):level = "INFO"messages =("Đã khởi động", "Đã kết nối")truyềnđầu vào đối số thườngphần còn lại thành tuple
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

Hãy tạo một hàm nhận bất kỳ số lượng giá tiền sản phẩm và trả về tổng của chúng.

① Định nghĩa def calc_total(*prices):return sum(prices).

② In kết quả của calc_total(300, 500)calc_total(120, 280, 550, 400). Xác nhận rằng cùng một hàm hoạt động bất kể bạn truyền vào bao nhiêu đối số.

(Khi chạy đúng, phần giải thích sẽ xuất hiện.)

Python Editor

Chạy code để xem đầu ra

Đố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).

Gom đối số từ khóa với **kwargs
phía gọishow(name="Minh", age=30)định nghĩadef show(**info):bên trong hàminfo = {"name": "Minh", "age": 30}kiểu: dicttruyềngom

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.

Đối số thông thường kết hợp với **kwargs
save_user("Minh", age=30, city="Hà Nội")def save_user(name, **info):name = "Minh"info ={"age": 30, "city": "Hà Nội"}truyềnđầu vào đối số thườngphần còn lại thành dict
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

Hãy viết một hàm nhận thông tin user dưới dạng đối số từ khóa và in mỗi mục trên một dòng riêng.

① Định nghĩa def show_profile(**info): và lặp với for key, value in info.items(): in f"{key}: {value}".

② Gọi nó với show_profile(name="Minh", age=20, city="TP.HCM").

Python Editor

Chạy code để xem đầu ra

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}

Hãy viết một hàm dùng đối số thông thường, *args, và **kwargs cùng lúc.

① Định nghĩa def register(user_id, *tags, **meta): và in ba dòng: f"id: {user_id}", f"tags: {tags}", và f"meta: {meta}".

② Gọi register(1001, "admin", "beta", email="minh@example.com", active=True) và xem mỗi đối số rơi vào đâu.

Python Editor

Chạy code để xem đầu ra

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.

Việc bạn có viết * hay không sẽ thay đổi nội dung của *args
lst = [1, 2, 3]f(lst)(không *)args =([1, 2, 3],)(một list)lst = [1, 2, 3]f(*lst)(có *)args =(1, 2, 3)(đã trải ra)vẫn là mộttrải ra
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.

Lấy list có sẵn cart_prices = [480, 1200, 320, 980]trải nó ra vào hàm calc_total ở phần trước.

① Định nghĩa lại def calc_total(*prices): trả về sum(prices).

② Khai báo cart_prices = [480, 1200, 320, 980]print() kết quả của calc_total(*cart_prices).

③ Thử print(calc_total(cart_prices)) không có dấu sao. Bạn sẽ gặp lỗi — đọc nó và xác nhận rằng chính list trở thành đối số đầu tiên.

Python Editor

Chạy code để xem đầu ra

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.).

Nhiều giá trị trả về quay lại dưới dạng tuple
def get_person():return name, age, city(name, age, city)(tuple)result = get_person()# result vẫn là tuplename, age, city= get_person()trải ra thànhname, age, citygiá trị thựcunpack
# 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.

Hãy viết một hàm trả về cả giá thấp nhất và cao nhất từ một list sản phẩm.

① Định nghĩa def get_price_range(prices):return min(prices), max(prices) để gửi lại hai giá trị.

② Chuẩn bị product_prices = [480, 1200, 320, 980, 650]unpack kết quả với lowest, highest = get_price_range(product_prices).

③ In f"Lowest: {lowest}"f"Highest: {highest}".

Python Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Code này in ra gì?
def f(*args):
return sum(args)

print(f(1, 2, 3, 4))

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)?