Giải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng

  1. Trang chủ
  2. Blog
  3. Phát triển phần mềm

Phát triển phần mềm

Giải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng

Những ngôi sao trong chữ ký chức năng ít bí ẩn hơn vẻ ngoài của chúng. Tìm hiểu cách *args và **kwargs thu thập các đối số, cách các ngôi sao giống nhau giải nén chúng và cách thiết kế các chữ ký hàm khó bị lạm dụng.

Đọc 8 phút
Python *args và **kwargs đã giải thích: các hàm linh hoạt với các ví dụ rõ ràng

Điểm chính

  • *args thu thập các đối số vị trí bổ sung vào một tuple.
  • **kwargs thu thập các đối số từ khóa bổ sung vào từ điển.
  • Các tên chỉ mang tính chất quy ước, vì * và ** mới là vấn đề quan trọng.
  • Tương tự như vậy giải nén một danh sách hoặc từ điển khi bạn gọi một hàm.

Sớm hay muộn, bạn sẽ đọc một hàm như def handler(*args, **kwargs) và tự hỏi nó chấp nhận điều gì. Python *args và **kwargs cho phép một hàm nhận số lượng đối số linh hoạt. Chúng cung cấp năng lượng cho các trình trang trí, trình bao bọc và nhiều API thư viện.

Hướng dẫn này trình bày cách chúng hoạt động theo cả hai hướng, cùng với những cách mới hơn mà Python cho phép bạn kiểm soát cách gọi một hàm. Chúng tôi đã chạy từng ví dụ trên Python 3.13 và kết quả hiển thị là từ lần chạy đó.

*args làm gì

Một tham số được viết bằng một dấu sao sẽ thu thập bất kỳ đối số vị trí bổ sung nào vào một bộ dữ liệu:

def total(*args):
    print(type(args).__name__, args)
    return sum(args)

print(total(1, 2, 3))
# tuple (1, 2, 3)
# 6

print(total())
# tuple ()
# 0

Không có đối số, bạn chỉ cần lấy một bộ dữ liệu trống để hàm vẫn hoạt động. Tên args là một quy ước. *numbers sẽ hoạt động giống nhau và tên mang tính mô tả nhiều hơn thường sẽ tốt hơn.

**kwargs làm gì

Hai ngôi sao thu thập bất kỳ đối số từ khóa bổ sung nào vào từ điển:

def describe(**kwargs):
    print(type(kwargs).__name__, kwargs)

describe(name="Asha", city="Pune")
# dict {'name': 'Asha', 'city': 'Pune'}

Bởi vì nó là một từ điển bình thường nên bạn có thể lặp lại nó, tra cứu các khóa bằng get() và chuyển nó đi. Các khóa giữ nguyên thứ tự mà chúng được chuyển.

Thứ tự tham số

Khi bạn kết hợp các loại tham số, Python yêu cầu một thứ tự cụ thể:

  1. Thông số vị trí thông thường
  2. *args
  3. Thông số chỉ từ khóa
  4. **kwargs
def order(item, qty=1, *extras, priority=False, **notes):
    print(item, qty, extras, priority, notes)

order("pen")
# pen 1 () False {}

order("pen", 2, "gift wrap", "card", priority=True, note="leave at door")
# pen 2 ('gift wrap', 'card') True {'note': 'leave at door'}

Thông báo priority. Bởi vì nó xuất hiện sau *extras nên nó chỉ có thể được đặt theo tên, do đó, một đối số vị trí sai lệch không bao giờ có thể vô tình đặt nó. Không được phép đặt **kwargs sớm hơn và thử nghiệm của chúng tôi đã đưa ra SyntaxError: ​​”đối số không thể tuân theo đối số từ khóa var”.

Giải nén khi gọi

Các ngôi sao tương tự hoạt động ngược lại tại vị trí cuộc gọi. Một ngôi sao trải một danh sách hoặc bộ dữ liệu thành các đối số vị trí và hai ngôi sao trải một từ điển thành các đối số từ khóa:

nums = [4, 5, 6]
total(*nums)            # same as total(4, 5, 6) -> 15

settings = {"name": "Ben", "city": "Delhi"}
describe(**settings)    # same as describe(name="Ben", city="Delhi")

Việc giải nén hoạt động với mọi chức năng, không chỉ những chức năng sử dụng *args và **kwargs. Điều đó rất hữu ích khi cấu hình xuất hiện dưới dạng từ điển:

def connect(host, port, timeout=5):
    return f"{host}:{port} (timeout {timeout})"

connect(**{"host": "db.local", "port": 5432})
# 'db.local:5432 (timeout 5)'

connect(**{"host": "db.local", "port": 5432, "ssl": True})
# TypeError: connect() got an unexpected keyword argument 'ssl'

Lỗi đó rất hữu ích. Hàm không có **kwargs sẽ từ chối các tùy chọn không xác định, do đó lỗi đánh máy trong tên cài đặt sẽ không thành công thay vì bị âm thầm bỏ qua.

Đối số chỉ từ khóa

Ký tự trống * trong chữ ký, không có tên, có nghĩa là “mọi thứ sau này phải được chuyển bằng từ khóa”:

def make_user(name, *, admin=False):
    return name, admin

make_user("Asha", admin=True)
# ('Asha', True)

make_user("Asha", True)
# TypeError: make_user() takes 1 positional argument but 2 were given

Đây là một cách tuyệt vời để bảo vệ cờ boolean. Cách gọi như make_user("Asha", True) buộc người đọc phải đoán True nghĩa là gì. Yêu cầu admin=True làm cho mọi cuộc gọi đều tự giải thích.

Đối số chỉ có vị trí

Kể từ Python 3.8, / đánh dấu các tham số chỉ có thể được truyền theo vị trí:

def area(width, height, /):
    return width * height

area(3, 4)                 # 12
area(width=3, height=4)
# TypeError: area() got some positional-only arguments
#   passed as keyword arguments: 'width, height'

Điều này cho phép thư viện đổi tên các tham số đó sau này mà không vi phạm mã của bất kỳ ai, vì không ai có thể phụ thuộc vào tên. Một số phần mềm tích hợp hoạt động theo cách này. Ví dụ: len(obj=[1]) không hoạt động.

Mẹo: Mẫu đầy đủ là def f(pos_only, /, normal, *, kw_only). Bạn hiếm khi cần cả ba, nhưng biết / và * nghĩa là gì sẽ giúp bạn đọc tài liệu và chữ ký thư viện.

Đồ trang trí và giấy gói

Đây là lý do chính trong thế giới thực để sử dụng cả hai cùng nhau. Một trình bao bọc thường cần chấp nhận bất kỳ đối số nào mà hàm ban đầu nhận và chuyển chúng không thay đổi:

import functools, time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def slow_add(a, b, delay=0.01):
    time.sleep(delay)
    return a + b

print(slow_add(2, 3, delay=0.02))
# slow_add took 0.0205s
# 5

Trình bao bọc hoàn toàn không cần biết các tham số của slow_add, vì vậy nó hoạt động với mọi chức năng. Con số thời gian của bạn sẽ khác một chút. Hướng dẫn của chúng tôi về Trình trang trí Python giải thích functools.wraps và các chi tiết khác.

Tùy chọn hợp nhất

** cũng hoạt động bên trong từ điển, cung cấp một cách gọn gàng để áp dụng các phần ghi đè lên trên các giá trị mặc định:

defaults = {"theme": "light", "lang": "en"}

def build(**overrides):
    return {**defaults, **overrides}

build(theme="dark")
# {'theme': 'dark', 'lang': 'en'}

Phím sau sẽ thắng nên ghi đè thay thế mặc định. Các lớp con cũng sử dụng mẫu này, như trong super().__init__(*args, **kwargs), để chuyển các đối số bổ sung cho lớp cha. Xem Các lớp và đối tượng Python để biết thêm về super().

Bảng cheat

Cú pháp Ở đâu Ý nghĩa
def f(*args) Định nghĩa Thu thập các đối số vị trí bổ sung vào một bộ dữ liệu
def f(**kwargs) Định nghĩa Thu thập các đối số từ khóa bổ sung vào một lệnh
định nghĩa f(a, *, b) Định nghĩa b chỉ có từ khóa
định nghĩa f(a, /, b) Định nghĩa a chỉ có vị trí
f(*items) Gọi Trải một chuỗi thành các đối số vị trí
f(**options) Gọi Truyền bá một lệnh vào các đối số từ khóa
{**a, **b} nghĩa đen Hợp nhất từ điển
[*a, *b] nghĩa đen Kết hợp các lần lặp vào một danh sách

Khi nào nên tránh chúng

Tính linh hoạt có giá của nó. Hàm được xác định là def create(**kwargs) không cho người đọc biết gì về những tùy chọn tồn tại và người chỉnh sửa không thể tự động hoàn thành chúng. Lỗi chính tả có thể diễn ra âm thầm qua nhiều lớp trước khi có bất kỳ lỗi nào xảy ra.

Vì vậy, hãy ưu tiên các tham số rõ ràng khi bạn biết hàm cần gì. Sử dụng *args và **kwargs khi bạn thực sự cần sự linh hoạt: trình bao bọc, trình trang trí, chuyển tiếp đối số sang hàm khác hoặc đầu vào có độ dài thay đổi thực sự như total(*numbers). Nếu một hàm cần nhiều tùy chọn được đặt tên thì dataclass cũng có thể rõ ràng hơn.

Những lỗi thường gặp

Sử dụng giá trị mặc định có thể thay đổi

Đây không phải là về các ngôi sao mà nó thường xuất hiện gần chúng. Giá trị mặc định được tạo một lần khi hàm được xác định và sau đó được chia sẻ giữa các lệnh gọi:

def bad(items=[]):
    items.append(1)
    return items

print(bad(), bad())
# [1, 1] [1, 1]

Cả hai cuộc gọi đều trả về cùng một danh sách, lúc đó chứa hai mục. Sử dụng items=None và tạo một danh sách mới bên trong hàm.

Các lỗi khác

  • Quên giải nén. Gọi total(nums) sẽ chuyển qua một danh sách thay vì ba số. Viết total(*nums).
  • Nuốt lỗi chính tả. Một hàm chấp nhận **kwargs nhưng bỏ qua các phím không xác định sẽ ẩn các tùy chọn sai chính tả. Xác thực chúng hoặc tránh **kwargs.
  • Truyền cùng một đối số hai lần. f(1, **{"a": 2}), trong đó a là tham số đầu tiên, sẽ gây ra lỗi “nhiều giá trị”.
  • Không chuyển tiếp trong trình bao bọc. Việc gọi func(args, kwargs) truyền một bộ dữ liệu và một lệnh. Dạng đúng là func(*args, **kwargs).

Nhập gợi ý cho *args và **kwargs

Gợi ý nhập vào *args và **kwargs mô tả từng giá trị riêng lẻ chứ không phải toàn bộ bộ sưu tập. Điều này khiến nhiều người ngạc nhiên:

def total(*prices: float) -> float:
    return sum(prices)      # prices is tuple[float, ...]

def tag(**attrs: str) -> str:
    return " ".join(f'{k}="{v}"' for k, v in attrs.items())   # dict[str, str]

total(1.5, 2.5)                  # 4.0
tag(id="x", role="button")       # id="x" role="button"

Ở đây prices được coi là một bộ số float và attrs là một từ điển của các chuỗi. Khi đối số từ khóa có các loại khác nhau, Python 3.12 đã thêm một tùy chọn chính xác: TypedDict kết hợp với Unpack.

from typing import TypedDict, Unpack

class Options(TypedDict, total=False):
    timeout: float
    retries: int

def fetch(url: str, **opts: Unpack[Options]) -> None: ...

Sau đó, trình kiểm tra loại như mypy hoặc Pyright có thể từ chối fetch(url, retries="3") hoặc tên tùy chọn sai chính tả, điều mà **kwargs đơn giản không bao giờ có thể làm được. Tuy nhiên, hành vi thời gian chạy không thay đổi. Khi chúng tôi chạy fetch("u", retries="3") trong Python 3.13, nó đã thực thi mà không có khiếu nại nào, vì bản thân Python không thực thi các gợi ý loại.

Nếu bạn thấy mình đang ghi lại một danh sách dài các khóa được chấp nhận cho **kwargs, hãy cân nhắc việc thay thế chúng bằng các tham số chỉ có từ khóa rõ ràng. Sau đó, người chỉnh sửa có thể tự động hoàn thành chúng và các lỗi sẽ được phát hiện ngay lập tức.

Lời kết

Hãy nhớ hai hướng. Theo một định nghĩa, các ngôi sao tập hợp lại. Trong một cuộc gọi, họ giải nén. Thêm đối số chỉ từ khóa để làm cho cuộc gọi rõ ràng hơn và dành **kwargs cho mã chuyển tiếp đối số thực sự. Để thực hành, hãy viết một trình trang trí nhỏ ghi lại mọi đối số của cuộc gọi và sử dụng nó trên một số chức năng khác nhau.

Câu hỏi thường gặp

Sự khác biệt giữa *args và **kwargs là gì?

*args thu thập các đối số vị trí bổ sung vào một bộ dữ liệu. **kwargs thu thập các đối số từ khóa bổ sung vào từ điển. Một chức năng có thể sử dụng cả hai cùng nhau.

Tôi có phải gọi chúng là args và kwargs không?

Không. Các ngôi sao thực hiện công việc, vì vậy các tùy chọn *giá trị hoặc ** đều hợp lệ như nhau. args và kwargs đơn giản là một quy ước được công nhận rộng rãi.

*args và **kwargs nên xếp theo thứ tự nào?

Đầu tiên là tham số thông thường, sau đó là *args, sau đó là tham số chỉ từ khóa và cuối cùng là **kwargs. Bất kỳ thứ tự nào khác đều gây ra Lỗi Cú pháp.

Một dấu * có nghĩa gì trong định nghĩa hàm?

Dấu * trần có nghĩa là mọi tham số sau nó phải được chuyển theo từ khóa. Nó làm cho cuộc gọi rõ ràng hơn, đặc biệt đối với các cờ boolean.

/có nghĩa là gì trong hàm Python?

Các tham số trước / chỉ mang tính vị trí, vì vậy người gọi không thể chuyển chúng theo tên. Tính năng này đã được thêm vào trong Python 3.8.

Tiếp tục học hỏi

Làm việc với Stack Quora

Cần trợ giúp về kiểm tra, WordPress hoặc SEO?

Chúng tôi giúp các nhóm phát triển phần mềm tốt hơn và phát triển trang web của họ. Hãy cho chúng tôi biết bạn đang làm gì và chúng tôi sẽ đề xuất bước tiếp theo rõ ràng.

Tiếp tục đọc

Thêm hướng dẫn về thử nghiệm, AI và phát triển hiện đại

Bạn thích bài viết này? Chọn một chủ đề và chuyển ngay sang phần hướng dẫn mới nhất của chúng tôi.

Trí tuệ nhân tạo

Giải thích về ảo giác AI: Tại sao các mô hình ngôn ngữ lại tạo nên mọi thứ và cách giảm thiểu nó

Ảo giác AI đã giải thích: chúng là gì, tại sao mô hình ngôn ngữ tạo ra chúng, các loại phổ biến, cách giảm thiểu chúng bằng cách căn cứ và nhắc nhở …

Đọc bài viết →
DevOps

Giải thích về các biến môi trường: Tệp .env, bí mật và cấu hình được thực hiện đúng

Các biến môi trường được giải thích bằng các ví dụ Python mà chúng tôi đã chạy: đọc và thiết lập chúng, tại sao mọi giá trị đều là một chuỗi, tệp .e…

Đọc bài viết →
Phát triển phần mềm

Giải thích về giới hạn tỷ lệ API: Thuật toán, tiêu đề và xử lý lỗi 429

Giới hạn tốc độ API được giải thích bằng mô phỏng mà chúng tôi đã chạy: nhóm mã thông báo, cửa sổ cố định, cửa sổ trượt, 429 phản hồi, tiêu đề giới …

Đọc bài viết →
Tự động hóa kiểm thử

Chế giễu bằng Python: unittest.mock, patch và những sai lầm khiến bài kiểm tra nói dối

Mô phỏng bằng Python với unittest.mock, đã được thử nghiệm: Mock và return_value, side_effect, vá lỗi nơi sử dụng tên, thông số kỹ thuật và thông số…

Đọc bài viết →
Kiểm thử phần mềm

Giải thích về phạm vi bảo hiểm của mã: Phạm vi tuyến so với chi nhánh và ý nghĩa thực sự 100%

Phạm vi bảo hiểm mã được giải thích bằng cách chạy phạm vi bảo hiểm thực tế: phạm vi bảo hiểm dòng, chi nhánh và điều kiện, đọc báo cáo, lý do tại s…

Đọc bài viết →
Cơ sở dữ liệu & SQL

DELETE vs TRUNCATE vs DROP trong SQL: Sự khác biệt, khôi phục và thời điểm sử dụng mỗi loại

DELETE vs TRUNCATE vs DROP được giải thích bằng SQL đã được thử nghiệm: mỗi loại loại bỏ, khôi phục, kích hoạt, đặt lại danh tính, tốc độ và Postgre…

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích về Chế độ xem SQL: Tạo, sử dụng và tránh những cạm bẫy, kèm theo ví dụ

Chế độ xem SQL được giải thích bằng các truy vấn chúng tôi đã chạy: tạo chế độ xem, kết quả trực tiếp, ẩn cột, hiệu suất, chế độ xem có thể cập nhật…

Đọc bài viết →
Phát triển phần mềm

Giao diện TypeScript so với loại: Sự khác biệt thực sự, được thử nghiệm bằng trình biên dịch

Giao diện TypeScript so với loại, được kiểm tra bằng trình biên dịch thực: cả hai có thể làm gì, chỉ một người có thể làm gì, hợp nhất khai báo, mở …

Đọc bài viết →
Phát triển phần mềm

Giải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng

Python *args và **kwargs được giải thích bằng mã chúng tôi đã chạy: đóng gói và giải nén, thứ tự tham số, đối số chỉ từ khóa và chỉ vị trí, trình tr…

Đọc bài viết →
Phát triển phần mềm

Hàm Lambda của Python: Cú pháp, cách sử dụng thực tế và khi nào không nên sử dụng chúng

Các hàm lambda của Python được giải thích bằng mã chúng tôi đã chạy: cú pháp, sắp xếp bằng khóa, bản đồ và bộ lọc, các phần đóng, bẫy liên kết muộn …

Đọc bài viết →
Trí tuệ nhân tạo

Đại lý AI là gì? Cách chúng hoạt động, với vòng lặp tác nhân đơn giản, bạn có thể chạy

Tác nhân AI là gì? Hướng dẫn đơn giản về vòng lặp tác nhân, công cụ, bộ nhớ và lan can, với một vòng lặp Python nhỏ mà chúng tôi đã chạy, những rủi …

Đọc bài viết →
DevOps

Giám sát và Khả năng quan sát: Sự khác biệt thực sự, với nhật ký, số liệu và dấu vết

Giải thích về giám sát và khả năng quan sát: lỗi đã biết và chưa biết, nhật ký, số liệu và dấu vết, hướng dẫn về sự cố thanh toán, tín hiệu vàng, Op…

Đọc bài viết →
DevOps

Docker Volumes vs Bind Mounts: Duy trì dữ liệu vùng chứa đúng cách

Giải thích về khối lượng Docker và liên kết gắn kết: tại sao dữ liệu vùng chứa biến mất, khối lượng được đặt tên, liên kết gắn kết, tmpfs, ví dụ về …

Đọc bài viết →
Tự động hóa kiểm thử

TDD vs BDD: Key Differences, One Feature Built Both Ways and When to Use Each

TDD và BDD được giải thích bằng một tính năng giảm giá được xây dựng theo cả hai cách: tái cấu trúc màu đỏ-xanh với mã chúng tôi đã chạy, kịch bản C…

Đọc bài viết →
Phát triển phần mềm

Git merge vs rebase: khác biệt, ví dụ và khi nào dùng cái nào

Git merge vs rebase được giải thích bằng đầu ra log thực tế, bảng so sánh, quy tắc vàng của rebase và quy trình làm việc nhóm an toàn.

Đọc bài viết →
Phát triển phần mềm

List comprehension trong Python: cú pháp, ví dụ và khi nào nên tránh

List comprehension trong Python: cú pháp, lọc dữ liệu, if-else, vòng lặp lồng nhau, thời gian đo thực tế và khi nào vòng lặp for thông thường tốt hơn.

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích SQL JOIN kèm ví dụ (Inner, Left, Right, Full)

Hiểu SQL JOIN từng bước với bảng và truy vấn mẫu: INNER, LEFT, RIGHT, FULL, CROSS và SELF, cùng lỗi thường gặp và mẹo hiệu năng.

Đọc bài viết →