Nhập chú thích cho * args và ** kwargs


158

Tôi đang thử các chú thích kiểu Python với các lớp cơ sở trừu tượng để viết một số giao diện. Có cách nào để chú thích các loại có thể *args**kwargs?

Ví dụ, làm thế nào một người có thể diễn tả rằng các đối số hợp lý cho một hàm là một inthoặc hai ints? type(args)đưa ra Tupledự đoán của tôi là chú thích loại như Union[Tuple[int, int], Tuple[int]], nhưng điều này không hoạt động.

from typing import Union, Tuple

def foo(*args: Union[Tuple[int, int], Tuple[int]]):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

# ok
print(foo((1,)))
print(foo((1, 2)))
# mypy does not like this
print(foo(1))
print(foo(1, 2))

Thông báo lỗi từ mypy:

t.py: note: In function "foo":
t.py:6: error: Unsupported operand types for + ("tuple" and "Union[Tuple[int, int], Tuple[int]]")
t.py: note: At top level:
t.py:12: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:14: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 2 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"

Điều này có nghĩa là mypy không thích điều này đối với lệnh gọi hàm vì nó hy vọng sẽ có một tuplecuộc gọi trong chính nó. Việc bổ sung sau khi giải nén cũng gây ra lỗi đánh máy mà tôi không hiểu.

Làm thế nào để một chú thích các loại hợp lý cho *args**kwargs?

Câu trả lời:


167

Đối với các đối số vị trí biến ( *args) và đối số từ khóa biến ( **kw), bạn chỉ cần xác định giá trị mong đợi cho một đối số như vậy.

Từ danh sách đối số tùy ý và phần giá trị đối số mặc định của Loại gợi ý PEP:

Danh sách đối số tùy ý cũng có thể được chú thích, sao cho định nghĩa:

def foo(*args: str, **kwds: int): ...

được chấp nhận và điều đó có nghĩa là, ví dụ, tất cả các lệnh gọi hàm đại diện sau với các loại đối số hợp lệ:

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)

Vì vậy, bạn muốn chỉ định phương pháp của bạn như thế này:

def foo(*args: int):

Tuy nhiên, nếu hàm của bạn chỉ có thể chấp nhận một hoặc hai giá trị số nguyên, bạn hoàn toàn không nên sử dụng *args, hãy sử dụng một đối số vị trí rõ ràng và đối số từ khóa thứ hai:

def foo(first: int, second: Optional[int] = None):

Bây giờ chức năng của bạn thực sự bị giới hạn ở một hoặc hai đối số và cả hai phải là số nguyên nếu được chỉ định. *args luôn luôn có nghĩa là 0 trở lên, và không thể bị giới hạn bởi loại gợi ý đến một phạm vi cụ thể hơn.


1
Chỉ tò mò, tại sao thêm Optional? Có điều gì đó thay đổi về Python hoặc bạn đã thay đổi suy nghĩ của mình? Nó vẫn không thực sự cần thiết do Nonemặc định?
Praxeolitic

10
@Praxeolitic có, trong thực tế, Optionalchú thích tự động, ngụ ý khi bạn sử dụng Nonelàm giá trị mặc định khiến cho các ứng dụng nhất định trở nên khó khăn hơn và hiện đang bị xóa khỏi PEP.
Martijn Pieters

5
Đây là một liên kết thảo luận về điều này cho những người quan tâm. Nó chắc chắn có vẻ như rõ ràng Optionalsẽ được yêu cầu trong tương lai.
Rick hỗ trợ Monica

Điều này thực sự không được hỗ trợ cho Callable: github.com/python/mypy/issues/5876
Shital Shah

1
@ShitalShah: đó không thực sự là vấn đề đó. Callablekhông hỗ trợ bất kỳ đề cập nào về một gợi ý loại cho *argshoặc **kwargs dừng hoàn toàn . Vấn đề cụ thể đó là về việc đánh dấu các tên gọi chấp nhận các đối số cụ thể cộng với số lượng tùy ý khác , và do đó *args: Any, **kwargs: Any, sử dụng , một gợi ý loại rất cụ thể cho hai hàm bắt. Đối với trường hợp bạn đặt *argsvà / hoặc **kwargsmột cái gì đó cụ thể hơn, bạn có thể sử dụng a Protocol.
Martijn Pieters

26

Cách thích hợp để làm điều này là sử dụng @overload

from typing import overload

@overload
def foo(arg1: int, arg2: int) -> int:
    ...

@overload
def foo(arg: int) -> int:
    ...

def foo(*args):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

print(foo(1))
print(foo(1, 2))

Lưu ý rằng bạn không thêm @overloadhoặc nhập chú thích vào việc triển khai thực tế, điều này phải đến sau cùng.

Bạn sẽ cần một phiên bản mới của cả hai typingvà mypy để nhận được hỗ trợ cho @overload bên ngoài các tệp sơ khai .

Bạn cũng có thể sử dụng điều này để thay đổi kết quả trả về theo cách làm rõ ràng loại đối số nào tương ứng với loại trả về. ví dụ:

from typing import Tuple, overload

@overload
def foo(arg1: int, arg2: int) -> Tuple[int, int]:
    ...

@overload
def foo(arg: int) -> int:
    ...

def foo(*args):
    try:
        i, j = args
        return j, i
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

print(foo(1))
print(foo(1, 2))

2
Tôi thích câu trả lời này vì nó giải quyết trường hợp tổng quát hơn. Nhìn lại, tôi không nên sử dụng (type1)so với (type1, type1)các cuộc gọi chức năng như ví dụ của tôi. Có lẽ (type1)vs (type2, type1)sẽ là một ví dụ tốt hơn và cho thấy lý do tại sao tôi thích câu trả lời này. Điều này cũng cho phép các loại trả lại khác nhau. Tuy nhiên, trong trường hợp đặc biệt khi bạn chỉ có một loại trả về và của bạn *args*kwargsđều cùng loại, kỹ thuật trong câu trả lời của Martjin có ý nghĩa hơn nên cả hai câu trả lời đều hữu ích.
Praxeolitic

4
Tuy nhiên, sử dụng *argsở nơi có số lượng đối số tối đa (2 ở đây) vẫn sai .
Martijn Pieters

1
@MartijnPieters Tại sao *argsnhất thiết phải sai ở đây? Nếu các cuộc gọi dự kiến ​​là (type1)vs (type2, type1), thì số lượng đối số là biến và không có mặc định thích hợp cho đối số theo dõi. Tại sao điều quan trọng là có tối đa?
Praxeolitic

1
*argsthực sự là có cho không hoặc nhiều hơn , các đối số đồng nhất, chưa được khai thác, hoặc cho 'vượt qua những điều này cùng với tất cả những gì bắt được'. Bạn có một đối số bắt buộc và một tùy chọn. Điều đó hoàn toàn khác biệt và thường được xử lý bằng cách cung cấp cho đối số thứ hai một giá trị mặc định để gửi đi để phát hiện ra rằng nó đã bị bỏ qua.
Martijn Pieters

3
Sau khi xem PEP, đây rõ ràng không phải là mục đích sử dụng của @overload. Mặc dù câu trả lời này cho thấy một cách thú vị để chú thích riêng các loại *args, nhưng một câu trả lời thậm chí tốt hơn cho câu hỏi là đây không phải là điều nên làm.
Praxeolitic

20

Là một bổ sung ngắn cho câu trả lời trước, nếu bạn đang cố gắng sử dụng mypy trên các tệp Python 2 và cần sử dụng các nhận xét để thêm các loại thay vì chú thích, bạn cần phải thêm tiền tố các loại cho argskwargsvới ***tương ứng:

def foo(param, *args, **kwargs):
    # type: (bool, *str, **int) -> None
    pass

Điều này được mypy coi là giống như bên dưới, phiên bản Python 3.5 của foo:

def foo(param: bool, *args: str, **kwargs: int) -> None:
    pass
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.