2016-05-04 46 views
24

我正在嘗試使用抽象基類來編寫一些接口的Python類型註釋。有沒有辦法對*args**kwargs的可能類型進行註釋?爲* args和** kwargs輸入註釋

例如,如何表示函數的合理參數是int還是兩個inttype(args)給出Tuple,所以我的猜測是將其註釋爲Union[Tuple[int, int], Tuple[int]],但這不起作用。從mypy

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

錯誤消息:

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

這是有道理的,因爲它需要有是在自稱爲tuple mypy不喜歡本作的函數調用。打開包裝後的添加也會導致我不明白的打字錯誤。

如何註釋*args**kwargs的合理類型?

回答

19

對於可變位置參數(*args)和可變的關鍵字參數(**kw),你只需要爲一個這樣的參數中指定的預期值。

類型的Arbitrary argument lists and default argument values section提示 PEP:

任意參數列表可作爲很好地類型註釋,使得定義:

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

是可接受的,並且它意味着,例如,以下全部代表具有有效類型參數的函數調用:

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

所以你要指定你這樣的方法:

def foo(*args: int): 

但是,如果你的函數只能接受一個或兩個整數值,則不應使用*args所有,使用一個明確的位置參數和第二關鍵字參數:

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

現在你的函數實際上是侷限於一個或兩個參數,如果指定了必須爲整數。 *args總是意味着0或更多,並且不能被類型提示限制到更具體的範圍。

+0

只是好奇,爲什麼添加'可選'? Python有沒有改變,或者你改變了主意?由於「無」默認值,它是否仍然不是必須的? – Praxeolitic

+3

@Praxeolitic是的,實際上,當你使用'None'作爲默認值時,默認的'Optional'註釋會使某些用例變得更難,並且現在正在從PEP中移除。 –

+0

[這裏是討論這個的鏈接](https://github.com/python/typing/issues/275)。這聽起來像明確的「可選」將在未來需要。 –

8

作爲短除了前面的答案,如果你想對Python的使用mypy 2個文件,需要使用註釋添加類型而不是註釋,你需要*的前綴類型argskwargs**分別爲:

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 
3

正確的方式做到這一點是使用@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或鍵入註釋到實際執行,它必須放在最後。

您需要一個新版本的typing和mypy才能獲得對@overload outside of stub files的支持。

您也可以用這種方式來改變返回的結果,使得明確哪些參數類型與哪種返回類型相對應。例如:

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

我喜歡這個答案,因爲它解決了一般情況。回想一下,我不應該使用'(type1)'和'(type1,type1)'函數調用作爲我的例子。也許'(type1)'vs'(type2,type1)'會是一個更好的例子,並說明我爲什麼喜歡這個答案。這也允許不同的返回類型。然而,在只有一種返回類型並且'* args'和'* kwargs'都是相同類型的特殊情況下,Martjin答案中的技術更有意義,因此兩種答案都很有用。 – Praxeolitic

+1

然而,如果有最大數量的參數(這裏是2),使用'* args' *仍然是錯誤的。 –

+0

所以,是的,很高興知道'@ overload',但這是針對此特定工作的錯誤工具*。 –

相關問題