args và kwargs trong Python

Table of Content

Đôi khi, khi nhìn vào một định nghĩa hàm trong Python, bạn có thể thấy rằng có hai tham số kỳ lạ *args**kwargs. Nếu bạn đã từng tự hỏi những biến đặc biệt này là gì hoặc tại sao IDE của bạn lại định nghĩa chúng trong hàm main(), thì bài viết này là dành cho bạn. Bạn sẽ học cách sử dụng argskwargs trong Python để thêm tính linh hoạt cho các hàm của mình.

Đến cuối bài này, bạn sẽ biết:

  • argskwargs thực sự có ý nghĩa gì?
  • Cách sử dụng *args**kwargs để định nghĩa hàm.
  • Cách sử dụng một dấu hoa thị (*) để giải nén các đối tượng có thể lặp.
  • Cách sử dụng hai dấu hoa thị (**) để giải nén một từ điển.

Truyền nhiều đối số cho một hàm

*args**kwargs cho phép bạn truyền nhiều đối số hoặc đối số có từ khóa cho một hàm. Hãy xem xét ví dụ sau. Đây là một ví dụ đơn giản nhận hai đối số và trả về tổng của chúng:

def my_sum(a, b):
    return a + b

Hàm này hoạt động tốt, nhưng nó chỉ giới hạn ở hai đối số. Điều gì sẽ xảy ra nếu bạn cần tính tổng của nhiều đối số khác nhau, trong đó số đối số chỉ được xác định khi chạy hàm?

Sử dụng biến args trong định nghĩa hàm

Có một số cách để bạn có thể chuyển một số đối số khác nhau vào một hàm. Cách đầu tiên, bạn chỉ cần chuyển một danh sách hoặc một tập hợp tất cả các đối số vào hàm của mình.

# sum_integers_args.py
def my_sum(my_integers):
    result = 0
    for x in my_integers:
        result += x
    return result

list_of_integers = [1, 2, 3]
print(my_sum(list_of_integers))

Cách triển khai này hoạt động, nhưng bất cứ khi nào bạn gọi hàm này, bạn cũng cần tạo một danh sách các đối số để chuyển cho nó. Điều này có thể gây bất biện, đặc biệt nếu bạn không biết trước tất cả các giá trị sẽ được đưa vào danh sách.

Đây là nơi *args có thể thực sự hũu ích, bởi vì nó cho phép bạn chuyển nhiều đối số vị trí khác nhau. Lấy ví dụ sau:

# sum_integers_args.py
def my_sum(*args):
    result = 0
    # Iterating over the Python args tuple
    for x in args:
        result += x
    return result

print(my_sum(1, 2, 3))

Trong ví dụ này, bạn không còn chuyển danh sách tới my_sum() nữa. Thay vào đó, bạn đang chuyển ba đối số vị trí khác nhau. my_sum() lấy tất cả các đối số vị trí được cung cấp trong đầu vào và đóng gói tất cả vào trong một tuple có tên args.

Lưu ý rằng args chỉ là một cái tên. Bạn không bắt buộc phải sử dụng tên args. Bạn có thể chọn bất cứ tên nào bạn thích, chẳng hạn như integers:

# sum_integers_args_2.py
def my_sum(*integers):
    result = 0
    for x in integers:
        result += x
    return result

print(my_sum(1, 2, 3))

Hàm vẫn hoạt động, ngay cả khi bạn đối tên args thành integers. Tất cả những gì quan trọng ở đây là bạn cần sử dụng toán tử giải nén (*).

Ghi nhớ rằng đối tượng có thể lặp mà bạn nhận được không phải là một danh sách mà là một tuple. Một tuple tương tự danh sách ở chỗ cả hai đều hỗ trợ cắt và lặp. Tuy nhiên, các tuple rất khác danh sách ở một khía cạnh: danh sách có thể thay đổi được, trong khi tuple thì không. Để kiểm tra điều này, hãy chạy đoạn mã sau. Tập lệnh này cố gắng thay đổi giá trị của danh sách:

# change_list.py
my_list = [1, 2, 3]
my_list[0] = 9
print(my_list)

Giá trị đầu tiên của danh sách được cập nhật thành 9. Nếu bạn thực thi tập lệnh này, bạn sẽ thấy rằng danh sách thực sự đã được sửa đổi:

$ python change_list.py
[9, 2, 3]

Giá trị đầu tiên không còn là 0 nữa, mà là giá trị được cập nhật 9. Bây giờ, hãy thử làm điều tương tự với một tuple:

# change_tuple.py
my_tuple = (1, 2, 3)
my_tuple[0] = 9
print(my_tuple)

Nếu bạn cố gắng thực thi tập lệnh này, bạn sẽ thấy rằng trình thông dịch Python trả về lỗi:

$ python change_tuple.py
Traceback (most recent call last):
  File "change_tuple.py", line 3, in <module>
    my_tuple[0] = 9
TypeError: 'tuple' object does not support item assignment

Điều này là do một tuple là một đối tượng bất biến, và các giá trị của nó không thể thay đổi sau khi gán. Hãy ghi nhớ điều này khi bạn đang làm việc với tuple và *args.

Sử dụng biến kwargs trong định nghĩa hàm

Được rồi, giờ thì bạn đã hiẻu *args dùng để làm gì, nhưng còn **kwargs?

**kwargs hoạt động giống như *args vậy, nhưng thay vì chấp nhận các đối số vị trí, nó chấp nhận các đối số có khóa (đối số được đặt tên). Lấy ví dụ sau:

# concatenate.py
def concatenate(**kwargs):
    result = ""
    # Iterating over the Python kwargs dictionary
    for arg in kwargs.values():
        result += arg
    return result

print(concatenate(a="Python", b="Is", c="Great", d="!"))

Chương trình trên cho ra kết quả sau:

$ python concatenate.py
PythonIsGreat!

Lưu ý rằng trong ví dụ trên, đối tượng có thể lặp lại là một từ điển. Nếu bạn cần lặp lại các giá trị của từ điển và muốn trả về giá trị này, như trong ví dụ trên, thì bạn phải sử dụng .values().

Trên thực tế, nếu bạn quên sử dụng phương thức này, bạn sẽ thấy mình đang lặp lại các khóa của kwargs, như trong ví dụ sau:

# concatenate_keys.py
def concatenate(**kwargs):
    result = ""
    # Iterating over the keys of kwargs
    for arg in kwargs:
        result += arg
    return result

print(concatenate(a="Python", b="Is", c="Great", d="!"))

Bây giờ, nếu bạn cố gắng thực hiện ví dụ này, bạn sẽ nhận thấy kết quả sau:

$ python contatenate_keys.py
abcd

Như bạn có thể thấy, nếu không chỉ định .values(), hàm của bạn lặp lại các khóa của từ điển kwargs, trả về kết quả sai.

Sắp xếp các đối số trong một hàm

Bây giờ bạn đã được học *args**kwargs dùng để làm gì, bạn đã sẵn sàng để bắt đầu viết các hàm sử dụng nhiều đối số khác nhau. Nhưng nếu bạn muốn tạo một hàm có truyền vào nhiều đối số vị trí lẫn đối số có tên thì phải làm sao?

Trong trường hợp này, bạn phải ghi nhớ thứ tự sau: trước tiên là đối số thông thường, sau đó là *args và cuối cùng là **kwargs.

Ví dụ, định nghĩa hàm sau là hoàn toàn đúng:

# correct_function_definition.py
def my_function(a, b, *args, **kwargs):
    pass

Giải nén bằng các toán tử dấu hoa thị: ***

Bây giờ bạn có thể sử dụng *args**kwargs để định nghĩa các hàm có thể nhận nhiều đối số đầu vào khác nhau. Hãy đi sâu hơn một chút để hiểu thêm về các toán tử giải nén.

Các toán tử giải nén *** đã được giới thiệu trong Python 2. Kể từ bản 3.5, chúng thậm chí còn trở nên mạnh mẽ hơn . Tóm lại, các toán tử giải nén là các toán tử cho phép giải nén các giá trị từ các đối tượng có thể lặp trong Python. Toán tử * có thể được sử dụng trên bất kỳ đối tượng có thể lặp nào mà Python cung cấp, trong khi toán tử ** chỉ có thể được sử dụng trên từ điển.

Hãy bắt đầu với một ví dụ:

# print_list.py
my_list = [1, 2, 3]
print(my_list)

Đoạn mã này định nghĩa một danh sách và in nó ra:

$ python print_list.py
[1, 2, 3]

Bây giờ, hãy thêm toán tử giải nén * vào trước tên danh sách của bạn:

# print_unpacked_list.py
my_list = [1, 2, 3]
print(*my_list)

Trong trường hợp này, đầu ra không còn là chính danh sách mà là nội dung của danh sách:

$ python print_unpacked_list.py
1 2 3

Ta có thể dùng kết quả trả về này để làm các đối số truyền vào hàm:

# unpacking_call.py
def my_sum(a, b, c):
    print(a + b + c)

my_list = [1, 2, 3]
my_sum(*my_list)

Nếu bạn chạy tập lệnh này, bạn nhận được tổng của ba số:

$ python unpacking_call.py
6

Nó kết hợp hoàn hảo với hàm được định nghĩa sử dụng *args:

# sum_integers_args_3.py
def my_sum(*args):
    result = 0
    for x in args:
        result += x
    return result

list1 = [1, 2, 3]
list2 = [4, 5]
list3 = [6, 7, 8, 9]

print(my_sum(*list1))
print(my_sum(*list2))
print(my_sum(*list3))
print(my_sum(*list1, *list2, *list3))

Nếu bạn chạy ví dụ này, từng danh sách được giải nén đồng thời 3 danh sách cùng được giải nén để tạo một danh sách bao gồm tất cả:

$ python sum_integers_args_3.py
6
9
30
45

Có một cách sử dụng thuận lợi khác của toán tử giải nén. Ví dụ, giả sử bạn cần chia danh sách thành ba phần khác nhau. Đầu ra phải hiển thị giá trị đầu tiên, giá trị cuối cùng và tất cả các giá trị ở giữa. Với toán tử giải nén, bạn có thể thực hiện việc này chỉ trong một dòng mã:

# extract_list_body.py
my_list = [1, 2, 3, 4, 5, 6]
a, *b, c = my_list

print(a)
print(b)
print(c)

Trong ví dụ này my_list có 6 mục. Mục đầu tiên được gán cho a, mục cuối cùng được gán cho c và tất cả các giá trị khác được đóng gói vào một danh sách mới b. Nếu bạn chạy tập lệnh này, print() sẽ cho bạn thấy rằng ba biến của bạn có các giá trị mong đợi:

$ python extract_list_body.py
1
[2, 3, 4, 5]
6

Một điều thú vị khác mà bạn có thể làm với toán tử giải nén * là chia nhỏ các mục bất kỳ của một đối tượng có thể lặp. Điều này có thể rất hữu ích nếu bạn cần hợp nhất hai danh sách, ví dụ:

# merging_lists.py
my_first_list = [1, 2, 3]
my_second_list = [4, 5, 6]
my_merged_list = [*my_first_list, *my_second_list]

print(my_merged_list)

Kết quả:

$ python merging_lists.py
[1, 2, 3, 4, 5, 6]

Bạn thậm chí có thể hợp nhất hai từ điển khác nhau bằng cách sử dụng toán tử giải nén **:

# merging_dicts.py
my_first_dict = {"A": 1, "B": 2}
my_second_dict = {"C": 3, "D": 4}
my_merged_dict = {**my_first_dict, **my_second_dict}

print(my_merged_dict)

Kết quả:

$ python merging_dicts.py
{'A': 1, 'B': 2, 'C': 3, 'D': 4}

Hãy nhớ rằng, toán tử * có thể hoạt động trên bất cứ đối tượng có thể lặp nào, gồm cả chuỗi:

# string_to_list.py
a = [*"Python"]
print(a)

Kết quả:

$ python string_to_list.py
['P', 'y', 't', 'h', 'o', 'n']

Leave a Reply