typing-python用於類型註解的庫

簡介

動態語言的靈活性使其在作一些工具,腳本時很是方便,可是同時也給大型項目的開發帶來了一些麻煩。python

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

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

伴隨着python3.6的pep526則更進一步引入了對變量類型的聲明,和在之前咱們只能在註釋中對變量的類型進行說明app

# 使用註釋來標明變量類型
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也可能會發生改變,即不保證向後兼容性async

咱們已經在簡介中介紹過類型註解,那麼除了默認類型的int、str用於類型註解的類型有哪些呢?ide

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

類型別名有助於簡化一些複雜的類型聲明ui

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來輔助函數創造不一樣的類型this

form typing import NewType

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

靜態類型檢查器將將新類型視爲原始類型的子類。這對於幫助捕獲邏輯錯誤很是有用scala

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()
    ...
相關文章
相關標籤/搜索