python用於型別註解的庫- typing

小小程序员ol發表於2024-06-05

一、簡介

動態語言的靈活性使其在做一些工具,指令碼時非常方便,但是同時也給大型專案的開發帶來了一些麻煩。

自python3.5開始,PEP484為python引入了型別註解(type hints),雖然在pep3107定義了函式註釋(function annotation)的語法,但仍然故意留下了一些未定義的行為.現在已經擁有許多對於靜態型別的分析的第三方工具,而pep484引入了一個模組來提供這些工具,同時還規定一些不能使用註釋(annoation)的情況

#一個典型的函式註釋例子,為引數加上了型別
def greeting(name: str) -> str:
    return 'Hello ' + name

伴隨著python3.6的pep526則更進一步引入了對變數型別的宣告,和在以前我們只能在註釋中對變數的型別進行說明

# 使用註釋來標明變數型別
primes = [] # type:list[int]
captain = ... #type:str

class Starship:
    stats = {} #type:Dict[str,int]
primes:List[int] = []
captain:str #Note: no initial value

class Starship:
    stats: ClassVar[Dict[str,int]] = {}

二、typing--對於type hints支援的標準庫

typing模組已經被加入標準庫的provisional basis中,新的特性可能會增加,如果開發者認為有必要,api也可能會發生改變,即不保證向後相容性

我們已經在簡介中介紹過型別註解,那麼除了預設型別的int、str用於型別註解的型別有哪些呢?

typing庫便是一個幫助我們實現型別註解的庫

型別別名(type alias)

在下面這個例子中,Vector和List[float]可以視為同義詞

from typing import List
Vector = List[float]

def scale(scalar: float, vector: Vector)->Vector:
    return [scalar*num for num in vector]

new_vector = scale(2.0, [1.0, -4.2, 5.4])

型別別名有助於簡化一些複雜的型別宣告

from typing import Dict, Tuple, List

ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]

def broadcast_message(message: str, servers: List[Server]) -> None:
    ...

# The static type checker will treat the previous type signature as
# being exactly equivalent to this one.
def broadcast_message(
        message: str,
        servers: List[Tuple[Tuple[str, int], Dict[str, str]]]) -> None:
    pass

新型別(New Type)

使用NewType來輔助函式創造不同的型別

form typing import NewType

UserId = NewType("UserId", int)
some_id = UserId(524313)

靜態型別檢查器將將新型別視為原始型別的子類。這對於幫助捕獲邏輯錯誤非常有用

def get_user_name(user_id: UserId) -> str:
    pass

# typechecks
user_a = get_user_name(UserId(42351))

# does not typecheck; an int is not a UserId
user_b = get_user_name(-1)

你仍然可以使用int型別變數的所有操作來使用UserId型別的變數,但結果返回的都是都是int型別。例如

# output仍然是int型別而不是UserId型別
output = UserId(23413) + UserId(54341)

雖然這無法阻止你使用int型別代替UserId型別,但可以避免你濫用UserId型別

注意,這些檢查僅僅被靜態檢查器強制檢查,在執行時Derived = NewType('Derived',base)將派生出一個函式直接返回你傳的任何引數,這意味著Derived(some_value)並不會建立任何新類或者建立任何消耗大於普通函式呼叫消耗的函式

確切地說,這個表示式 some_value is Derived(some_value) 在執行時總是對的。

這也意味著不可能建立派生的子型別,因為它在執行時是一個標識函式,而不是一個實際型別:

from typing import NewType

UserId = NewType('UserId', int)

# Fails at runtime and does not typecheck
class AdminUserId(UserId): pass

然而,它可以建立一個新的型別基於衍生的NewType

from typing import NewType

UserId = NewType('UserId', int)

ProUserId = NewType('ProUserId', UserId)

然後對於ProUserId的型別檢查會如預料般工作

Note:回想一下,使用型別別名宣告的兩個型別是完全一樣的,令Doing = Original將會使靜態型別檢查時把Alias等同於Original,這個結論能夠幫助你簡化複雜的型別宣告

與Alias不同,NewType宣告瞭另一個的子類,令Derived = NewType('Derived', Original)將會使靜態型別檢查把Derived看做Original的子類,這意味著型別Original不能用於型別Derived,這有助於使用最小的消耗來防止邏輯錯誤。

回撥(callable)

回撥函式可以使用類似Callable[[Arg1Type, Arg2Type],ReturnType]的型別註釋

例如

from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    # Body

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body

可以透過對型別提示中的引數列表替換一個文字省略號來宣告一個可呼叫的返回型別,而不指定呼叫引數,例如 Callable[..., ReturnType]

泛型(Generics)

因為容器中的元素的型別資訊由於泛型不同透過一般方式靜態推斷,因此抽象類被用來擴充表示容器中的元素

from typing import Mapping, Sequence

def notify_by_email(employees: Sequence[Employee],
                    overrides: Mapping[str, str]) -> None: ... 

可以透過typing中的TypeVar將泛型引數化

from typing import Sequence, TypeVar

T = TypeVar('T')      # 申明型別變數

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

使用者定義泛型型別

from typing import TypeVar, Generic
from logging import Logger

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('%s: %s', self.name, message)

定義了Generic[T]作為LoggedVar的基類,同時T也作為了方法中的引數。

透過Generic基類使用元類(metaclass)定義__getitem__()使得LoggedVar[t]是有效型別

from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)

泛型可以是任意型別的變數,但也可以被約束

from typing import TypeVar, Generic
...

T = TypeVar('T')
S = TypeVar('S', int, str)

class StrangePair(Generic[T, S]):
    ...

每個型別變數的引數必須是不同的

下面是非法的

from typing import TypeVar, Generic
...

T = TypeVar('T')

class Pair(Generic[T, T]):   # INVALID
    ...

你可以使用Generic實現多繼承

from typing import TypeVar, Generic, Sized

T = TypeVar('T')

class LinkedList(Sized, Generic[T]):
    ... 

當繼承泛型類時,一些型別變數可以被固定

from typing import TypeVar, Mapping

T = TypeVar('T')

class MyDict(Mapping[str, T]):
    ...

使用泛型類而不指定型別引數則假定每個位置都是Any,。在下面的例子中,myiterable不是泛型但隱式繼承Iterable [Any]

from typing import Iterable

class MyIterable(Iterable): # Same as Iterable[Any]

還支援使用者定義的泛型型別別名。例項:

from typing import TypeVar, Iterable, Tuple, Union
S = TypeVar('S')
Response = Union[Iterable[S], int]

# Return type here is same as Union[Iterable[str], int]
def response(query: str) -> Response[str]:
    ...

T = TypeVar('T', int, float, complex)
Vec = Iterable[Tuple[T, T]]
#學習中遇到問題沒人解答?小編建立了一個Python學習交流群:153708845

def inproduct(v: Vec[T]) -> T: # Same as Iterable[Tuple[T, T]]
    return sum(x*y for x, y in v)

Generic的元類是abc.ABCMeta的子類,泛型類可以是包含抽象方法或屬性的ABC類(A generic class can be an ABC by including abstract methods or properties)

同時泛型類也可以含有ABC類的方法而沒有元類衝突。

Any

一種特殊的型別是。靜態型別檢查器將將每個型別視為與任何型別和任何型別相容,與每個型別相容。

from typing import Any

a = None    # type: Any
a = []      # OK
a = 2       # OK

s = ''      # type: str
s = a       # OK

def foo(item: Any) -> int:
    # Typechecks; 'item' could be any type,
    # and that type might have a 'bar' method
    item.bar()
    ...

相關文章