Python类型注解

Python类型注解,第1张

类型注解

有句话说的好,“动态一时爽,重构火葬场”。因此,python在3.5版本的时候引入了类型注解,以方便静态类型检查工具,IDE等第三方工具。例如,在vscode中只要你安装了python相关的插件之后,当你在编写如下的代码的时候,是没有任何提示的。

def fun(data):
    data.update({"a": 1})

但是,如果你对data做了注解,那么将会有相应的提示。

def fun(data:Dict[str, int]):
    data.update({"a": 1})

这是类型注解带来的好处之一。(如果你是使用Pycharm这样的IDE,那么你不使用类型注解也会有自动提示。)我们最关心的依旧是通过类型注解对动态语言做静态类型检查,来避免一些潜在的错误。

内置类型注解 非容器类型

对于非容器类型而言,类型注解的使用是非常简单的。例如:

a:int = 1
b:float = 3.1415926
c:bool = False
d:str = "qwert"

直接在变量后面跟上“:”,然后指明类型即可。

容器类型

如果你的只希望指明容器类型本身,那么容器类型注解使用起来非容器类型是类似的。例如:

a:dict = {"a": 1, "q": 123, 123: 456}
b:list = [1, 12.2, 212]
c:tuple = ("qwe", 123)    
d:set = set(range(10))

如果你想指定容器内数据详细的数据类型,那么在python3.9之前的版本,需要从typing模块导入相应的注解函数,然后进行注解。例如:

from typing import Dict, List, Optional, Tuple, Set, Union
a:Dict[Union[str, int], int] = {"a": 1, "q": 123, 123: 456}     
b:List[Union[int, float]] = [1, 12.2, 212]
c:Tuple[str, int] = ("qwe", 123)
c1:Tuple[Union[int ,str], ...] = (12,'11',33)
d:Set[int] = set(range(10))
e:Optional[str] = None

下面来解释一下Dict, List, Optional, Tuple, Set, Union它们的作用。需要注意的是,这不是函数调用,这里使用[]包括起来,而非().

  • Union表示只要是[]中任意一种类型即可。

      参数必须是某种类型,且至少有一个。
      联合类型之联合类型会被展平,例如:
      Union[Union[int, str], float] == Union[int, str, float]
      单参数之联合类型就是该参数自身,例如:
      Union[int] == int  # The constructor actually returns int
      冗余的参数会被跳过,例如:
      Union[int, str, int] == Union[int, str] == int | str
      比较联合类型,不涉及参数顺序,例如:
      Union[int, str] == Union[str, int]
    
  • Dict中的第一个值是键的类型,第二个值是值的类型

  • List只能有一个值,因此如果其中包含多种类型,需要使用Union

  • Tuple比较特殊,由于tuple是不可变类型,因此上面c:Tuple[str, int]写法实际表示tuple中只能有两个元素,且第一个元素是str类型,第二个元素是int类型。但是下一行c1:Tuple[Union[int ,str], ...]使用了省略号来表示比较大的元组

  • Optional的含义实际上和Union[X, None]是一致的。

在python3.9+的版本上,支持内置类型直接进行注解,无需从typing模块导入,简化了注解方式,在python3.7起可以使用from future import annotations来支持内置类型直接注解。如下所示:

a:dict[str, int] = {"abc": 123}
b:list[Union[int, str]] = [123,"123"]
类型别名

类型别名类似于C/C++中的typedef,它的使用可以给代码带来更加明确友好的说明,例如:

Vector_int = List[int]      # python3.9之前支持的写法
Vector_str = list[str]      # python3.9之后支持的写法
c:Vector_int = [1,2,3,4]
d:Vector_str = ['q', 'w', 'e']

Vector_int就是给List[int]起了个别名;同理,Vector_str就是给list[str]起了个别名。后续就可以使用Vector_int来声明变量的类型。类型别名适合用来简化复杂的类型。例如:

Port = int 
Scheme = str
Path = str
URL = Tuple[Scheme, Path, Port]

url:URL = ("https://", "home", 443)     # 复杂类型URL

除此之外,typing模块还提供了一个NewType方法,来辅助创建不同类型。例如:

from typing import NewType

int16 = NewType("int16", int)       # 将产生一个 int16 类,该类立即返回你传递给它的任何参数。
int32 = NewType("int32", int)
a:int16 = int16(123)                # 语句 int16(123) 不会创建一个新的类,也不会引入超出常规函数调用的很多开销。
b:int32 = int32(1234567)
c:int16 = 123            # 无法通过静态检查,因为int16是int的子类。这不是C++,父类指针不能 *** 作子类对象。

注意,使用class来派生NewType创建的类型,是非法的;但是可以使用NewType来继续派生NewType创建的子类。例如:

from typing import NewType

UserId = NewType('UserId', int)
ProUserId = NewType('ProUserId', UserId)

警告:python3.10中NewType 现在是一个类而不是一个函数。在调用 NewType时,会有一些额外的运行时间成本。这个成本将在3.11.0中降低

对于Python而言,一般我们是不会使用NewTpye来产生子类型,更多的可能是使用class去派生内置类型,然后进行 *** 作。例如:

class INT16(int):
    "有符号16整形"
    def __init__(self, num:int) -> None:
        if -32768 <= num < 32767:
            super().__init__()
        else: 
            raise Exception("Number out of range")

no:INT16 = INT16(-32768)
print(no)

上面的代码也展示了两种类型注释。一是使用自定义类来进行注释;二是对函数的返回值进行注释(使用-> 类型)。

可调用对象的类型注解

使用typing模块提供的Callable[[ArgType], ReturnType]来完成。例如:

def func(a:int, b:float) ->str :
    return str(a + b)

add:Callable[[int, float], str] = func     # 
print(add(1, 2))

add作为一个可调用对象,我们对其进行类型注解。上面这种场景可能不是很常见,更常见的可能是在装饰器这里使用Callable来进行注解。例如:

def outer(f:Callable[[str, int, str], None]) -> Callable[[str, int, str], None]:
    def inner(a:str , b: int, c: str) -> None:
        print("decorator starting")
        f(a, b, c)
        print("decorator end")
    return inner

@outer
def fun1(name:str, age:int, sex:str) -> None:
    print("name:", name)
    print("age:", age)
    print("sex:", sex)

fun1("Zhao si", 22, "男")
Any类型

Any类型是一种特殊的类型,静态类型检查器会将每种类型视与Any类型兼容。例如:

c:Any = 123
c = "123"       # OK

所有没有返回类型或参数类型的函数都将隐式默认为Any注解,当你需要混合动态和静态类型的代码时,Any是一个非常好的选择。

另外,对于没有返回值的函数(特指抛出异常的函数),可以使用NoReturn进行注解。例如:

from typing import NoReturn

def raise_exception(b:bool) -> Union[NoReturn, str]:
    if not b:
        raise Exception("ERROR")
    else:
        return "OK"

res:Union[NoReturn, str] = raise_exception(False)

基本上,常用的类型注解就是以上这些了,关于类型注解的更多内容,可以参考typing.

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/743151.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-28
下一篇2022-04-28

发表评论

登录后才能评论

评论列表(0条)

    保存