天天看點

typing類型注解庫

簡介

動态語言的靈活性使其在做一些工具,腳本時非常友善,但是同時也給大型項目的開發帶來了一些麻煩。

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

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()
    ...           
typing類型注解庫

如果這篇文章幫助到了你,你可以請作者喝一杯咖啡

typing類型注解庫
#一個典型的函數注釋例子,為參數加上了類型
def greeting(name: str) -> str:
    return 'Hello ' + name           
# 使用注釋來标明變量類型
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]] = {}           

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           

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)           
# output仍然是int類型而不是UserId類型
output = UserId(23413) + UserId(54341)           

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           
from typing import NewType

UserId = NewType('UserId', int)

ProUserId = NewType('ProUserId', UserId)           

Doing = Original

Derived = NewType('Derived', Original)

Derived

Original

Original

Derived

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]

from typing import Mapping, Sequence

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

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)           

__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
    ...           
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

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

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

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