web-dev-qa-db-ja.com

* argsおよび** kwargsの型注釈

いくつかのインターフェイスを記述するために、抽象基底クラスを使用してPythonの型注釈を試しています。可能なタイプの*argsおよび**kwargsに注釈を付ける方法はありますか?

たとえば、関数への適切な引数がintまたは2つのintsであることをどのように表現しますか? type(args)Tupleを与えるので、私の推測では型にUnion[Tuple[int, int], Tuple[int]]として注釈を付けることでしたが、これは機能しません。

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

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]]"

Mypyは、呼び出し自体にTupleがあることを期待しているため、関数呼び出しに対してmypyがこれを好まないことは理にかなっています。解凍後の追加も、私には理解できないタイプミスを与えます。

*argsおよび**kwargsの賢明なタイプにどのように注釈を付けますか?

85
Praxeolitic

可変位置引数(*args)および可変キーワード引数(**kw)の場合、oneそのような引数の期待値のみを指定する必要があります。

Type Hints PEPの 任意の引数リストとデフォルトの引数値セクション から:

任意の引数リストにも型注釈を付けることができるため、定義は次のようになります。

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

許容され、それは、例えば、以下のすべてが有効なタイプの引数を持つ関数呼び出しを表すことを意味します:

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

したがって、次のようにメソッドを指定する必要があります。

def foo(*args: int):

ただし、関数が1つまたは2つの整数値しか受け入れられない場合は、*argsをまったく使用せず、1つの明示的な位置引数と2番目のキーワード引数を使用する必要があります。

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

現在、関数は実際には1つまたは2つの引数に制限されており、指定する場合は両方とも整数でなければなりません。 *argsalwaysは0以上を意味し、タイプヒントによってより具体的な範囲に制限することはできません。

87
Martijn Pieters

前の回答への短い追加として、Python 2ファイルでmypyを使用し、注釈の代わりにコメントを使用して型を追加する必要がある場合、argsおよびkwargsの型の前に*および**それぞれ:

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

これはmypyによって以下と同じものとして扱われます。Python 3.5バージョンのfoo

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

これを行う適切な方法は @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))

@overloadを追加したり、実際の実装に注釈を入力したりしないでください。これは最後に来る必要があります。

@overload スタブファイルの外側 のサポートを取得するには、typingとmypyの両方の新しいバージョンが必要です。

また、これを使用して、どの引数タイプがどの戻りタイプに対応するかを明示する方法で、返される結果を変えることができます。例えば。:

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))
12
chadrik