語境
我剛剛閱讀了PEP 586。作者在動機中說:
numpy.unique 將回傳單個陣列或包含兩個到四個陣列的元組,具體取決于三個布爾標志值。
(...)
目前沒有辦法表達這些函式的型別簽名:PEP 484 不包含任何用于撰寫簽名的機制,其中回傳型別根據傳入的值而變化。
我們建議添加文字型別來解決這些差距。
但我真的不明白添加Literal型別對此有何幫助。而且我也不同意這樣的說法
PEP 484 不包括任何用于撰寫簽名的機制,其中回傳型別根據傳入的值而變化。
據我了解,Union可以在這種情況下使用。
問題
如何numpy.unique用 注釋回傳型別Literal?
uj5u.com熱心網友回復:
Literal解決的問題
您從 PEP 586 中完全摘取了正確的段落。為了再次突出此處的兩個關鍵詞,這是關于
回傳型別根據傳入的值而變化的簽名。
這是該Literal型別的應用之一。而且這個說法實際上是正確的。
您能否注釋一個在以前的各種(未進一步定義)情況下回傳兩種不同型別之一的函式?當然,正如您正確指出的那樣,Union可以用于此目的。
您能否注釋一個函式,該函式根據傳遞給它的不同引數型別(或其組合)回傳兩種不同型別之一?是的,這就是@overload裝飾器的用途。
但是根據傳遞給它的引數的值來注釋一個回傳兩種不同型別之一的函式?這在以前是不可能的Literal。
為了實作這一點,我們現在與裝飾器Literal結合使用。@overload在我們開始之前,請考慮以下示例np.unique。
簡單的例子
假設我有一個非常愚蠢的函式,它將作為引數傳遞給它的double加倍。float但如果還設定了特殊標志,它可以float再次回傳 a 或將其回傳為 a str:
from typing import Union
def double(
num: float,
as_string: bool = False,
) -> Union[float, str]:
num *= 2
if as_string:
return str(num)
return num
現在,這個注釋非常好。回傳型別捕獲兩種可能的情況,float以及str正在回傳的情況。
但是,現在說我有另一個只接受a 的函式str:
def need_str(x: str) -> None:
print(x.startswith("2"))
如果我想將doubleas 引數的輸出傳遞給 ,我該怎么辦need_str?
output = double(1.1, as_string=True)
need_str(output)
這是嚴格型別檢查器的問題。雖然代碼運行良好,因為我們知道,因為我們通過as_string=True, theoutput是一個字串。靜態型別檢查器(mypy這里)只看到第一個函式的回傳型別和第二個函式的引數型別,并正確地抱怨:
error: Argument 1 to "need_str" has incompatible type "Union[float, str]"; expected "str" [arg-type]
它認為這output很可能是一個float. 它不知道double里面做什么。我們如何解決這個問題?好吧,之前Literal,我能想到的最簡單的解決方案是做這樣的事情:
output = double(1.1, as_string=True)
assert isinstance(output, str)
need_str(output)
這是合理的,滿足型別檢查器并完成作業。
但是現在我們有了Literal,我們可以(可以說)更優雅地解決這個問題:
from typing import Literal, Union, overload
@overload
def double(
num: float,
as_string: Literal[False],
) -> float: ...
@overload
def double(
num: float,
as_string: Literal[True],
) -> str: ...
def double(
num: float,
as_string: bool = False,
) -> Union[float, str]:
num *= 2
if as_string:
return str(num)
return num
現在,如果我再試一次,型別檢查器會理解對 的特定呼叫double,推斷回傳值是型別str并認為下一個函式呼叫是型別安全的:
output = double(1.1, as_string=True)
need_str(output)
添加reveal_type(output)makemypy告訴我們Revealed type is "builtins.str"。
我希望這說明了它引入的功能,并且它們以前不存在。您可以使用 做其他事情Literal,但那是題外話。
這有什么幫助np.unique
正如您鏈接的檔案所揭示的那樣,np.unique基本上有四種不同的可能回傳型別:
- 一組相同
dtype的ar - 一個相同陣列的 2 元組,
dtype后跟ar一個整數陣列 - 一個相同的陣列的三元組,
dtype后跟ar兩個整數陣列 - 一個相同的陣列的 4 元組,
dtype后跟ar三個整數陣列
它是哪種型別(以及值的含義)完全取決于傳遞給引數、和的值:return_indexreturn_inversereturn_counts
- 如果所有這些引數都是
False(默認) - 如果這些論點之一是
True - 如果其中兩個論點是
True - 如果所有這三個論點都是
True
因此,情況類似于上面的簡單示例。只是還有更多需要@overloads定義,因為我們有2 3 = 8個引陣列合要反映在我們的呼叫中。
現在,如果我手頭有太多時間,想寫一個無用的包裝器np.unique,我將演示如何Literal正確地注釋所有不同的呼叫變體并滿足最嚴格的型別檢查器......
*嘆*
一個無用的包裝器np.unique
from collections.abc import Sequence
from typing import Literal, TypeAlias, TypeVar, Union, overload
import numpy as np
from numpy.typing import NDArray
T = TypeVar("T", bound=np.generic)
NPint: TypeAlias = np.int_
# All options `False`:
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[False],
return_inverse: Literal[False],
return_counts: Literal[False],
) -> NDArray[T]: ...
# One option `True`:
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[True],
return_inverse: Literal[False],
return_counts: Literal[False],
) -> tuple[NDArray[T], NDArray[NPint]]: ...
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[False],
return_inverse: Literal[True],
return_counts: Literal[False],
) -> tuple[NDArray[T], NDArray[NPint]]: ...
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[False],
return_inverse: Literal[False],
return_counts: Literal[True],
) -> tuple[NDArray[T], NDArray[NPint]]: ...
# Two options `True`:
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[True],
return_inverse: Literal[True],
return_counts: Literal[False],
) -> tuple[NDArray[T], NDArray[NPint], NDArray[NPint]]: ...
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[True],
return_inverse: Literal[False],
return_counts: Literal[True],
) -> tuple[NDArray[T], NDArray[NPint], NDArray[NPint]]: ...
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[False],
return_inverse: Literal[True],
return_counts: Literal[True],
) -> tuple[NDArray[T], NDArray[NPint], NDArray[NPint]]: ...
# Three options `True`:
@overload
def np_unique(
items: Sequence[T],
*,
return_index: Literal[True],
return_inverse: Literal[True],
return_counts: Literal[True],
) -> tuple[NDArray[T], NDArray[NPint], NDArray[NPint], NDArray[NPint]]: ...
def np_unique(
items: Sequence[T],
*,
return_index: Literal[True, False] = False,
return_inverse: Literal[True, False] = False,
return_counts: Literal[True, False] = False,
) -> Union[
NDArray[T],
tuple[NDArray[T], NDArray[NPint]],
tuple[NDArray[T], NDArray[NPint], NDArray[NPint]],
tuple[NDArray[T], NDArray[NPint], NDArray[NPint], NDArray[NPint]],
]:
return np.unique(
np.array(items),
return_index=return_index,
return_inverse=return_inverse,
return_counts=return_counts,
)
值得注意的是,在如此廣泛的多載下,理論上的可能性要大得多。如果碰巧其中一個選項會產生另一個不同 dtype元素的陣列,我們仍然可以在此處正確注釋該案例。
還值得一提的是,恕我直言,這太過分了。我不認為這是一種好的風格。一個函式不應該有這么多根本不同的呼叫簽名。這就是一些人所說的“代碼氣味”......
但至于打字功能,我說最好有它而不需要它,而不是相反。
希望這可以幫助。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/518293.html
上一篇:跨兩個2dNumPy陣列的一行中所有元素之間的差異?
下一篇:洗掉numpy陣列中的連續重復項
