定义:如果类型检查发生在编译阶段(compile time),那么是静态类型语言(statically typed languages)中,相反的,如果类型检查发生在运行阶段(run time),那么是动态类型语言(dynamically typed languages)
类型检查:就是查看变量的类型,然后判断这个表达式是合理的,可以这么理解:编译器通常在编译(尚未运行)阶段做类型检查(java),而解释器通常在运行阶段做类型检查(python);
示例:
public static void main(String[] args){ int a = "aaaa"; // 此处在ideal中会显示报错,但是在`.java`的文件中是没有提示的; System.out.println("Hello World"); }
执行编译命令的时候会提示报错,
javac file.java # 编译报错,提示类型错误
Python 中不存在编译的步骤,直接按行解释运行。
a = 'aaa' # 赋值后, a 才是 str 类型。 print("Hello World")
static:所有的变量类型必须被显示地声明,因为这些信息在编译阶段就被需要(java).
// java int a = 1; // 定义变量必须显示声明类型
Dynamic:显示声明不被要求,因为类型赋值发生在运行阶段(python).
# python i = 0.5 # 定义变量不需要显示声明,不用声明变量类型。
概念:强类型语言有更强的类型检查机制,表达式计算中会做严格的类型检查;而弱类型语言允许各种变量类型间做一些运算。
# Python s = "aaa" print(s+123) # 报错,
python 中比较特殊的就是字符串有一个乘法。
s = "aaa" print(s*2) >>> aaaaaa
java 中对变量的计算要求的更加严格,也是强类型语言。
let a = 1; let b = '2' a+b >>> '12'
js 中会在表达式自动将变量的类型进行提高;
Python 运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。
简单的介绍:声明式类型检查,但不是强制的,语法是名称:类型
。
a:int = 3 # 表明是 int形式,提高可读性 b:int = "aaa" # 运行不会报错,但是pycharm会显示警告;并且容易造成阅读混乱
函数中的检查
# 力扣 中算法的常见形式。 def add(a:int,b:int) -> int: # 同样是只做提高可读性,没有实际的限制。 return a+b
什么时候做类型检查?
重点:Python 中类型检查是运行时,如果大量的类型检查会有一定的性能消耗。正常我们开发都是以模块为单位,我们只在暴露给外部其他模块调用的方法做类型检查,自身内部的调用不做类型检查.
Python是一门动态语言,很多时候我们可能不清楚函数参数类型或者返回值类型,很有可能导致一些类型没有指定方法,在写完代码一段时间后回过头看代码,很可能忘记了自己写的函数需要传什么参数,返回什么类型的结果,就不得不去阅读代码的具体内容,降低了阅读的速度,typing模块可以很好的解决这个问题。
自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:
常用类型:
typing 模块最基本的支持有Any
,Tuple
,Callable
,TypeVar
和 Generic
类型组成。
使用NewType
创建不同的类型,静态检查器会将新类型视为原始类型的子类;
from typing import NewType UserId = NewType('UserId',int) def get_user(user_id:UserId): print(user_id) user_a = get_user(UserId(123)) # 如果直接传入别的形式的参数,也不报错,但是pycharm 会检查报错;
类似于 Java 中的参数自定义的了类的类型;进行返回。
注意:这些检查仅通过静态类型检查程序来强制。NewType
返回的是一个函数该函数立即返回传递它的任意值这就意味着UserId(1234)
并不会创建一个新的类或引入任何超出常规函数调用的开销。因此,运行过程中same_value is Newtype("TypeName", Base)(same_value)
始终为True。但是,可以基于 NewType
创建 NewType
。
class typing.List(list, MutableSequence[T])
list的泛型版本。用于注释返回类型。要注释参数,最好使用抽象集合类型,如Sequence或Iterable。示例:
from typing import TypeVar,List T = TypeVar('T',int,float) def func(x:T,y:T)-> List[T]: return [x,y] print(func(1,1)) # dict 的泛型版本。对标注返回类型比较有用。如果要标注参数的话,使用如 Mapping 的抽象容器类型是更好的选择。示例: def count_words(text: str) -> Dict[str, int]: pass
更多用法,请参考https://www.jianshu.com/p/9b6b9a06cd3e
class typing.Iterable(Generic[T_co]) # 要注释函数参数中的迭代类型时,推荐使用的抽象集合类型。 class typing.Sequence(Reversible[T_co], Collection[T_co]) # 要注释函数参数中的序列例如列表类型时,推荐使用的抽象集合类型。 class typing.Mapping(Sized, Collection[KT], Generic[VT_co]) # 要注释函数参数中的Key-Value类型时,推荐使用的抽象集合类型。
# 不常用 class typing.TypeVar
需要注意的是 TypeVar
不是一个类使用 isinstance(x, T)
会在运行时抛出 TypeError
异常。一般地说, isinstance()
和 issubclass()
不应该和类型变量一起使用。示例:
from typing import TypeVar T = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # 限制字符串或者字节数据 def repeat(x: T, n: int) -> Sequence[T]: """ Return a list containing n references to x. """ return [x]*n def longest(x: A, y: A) -> A: """Return the longest of two strings.""" return x if len(x) >= len(y) else y
不太常用,通常是直接使用str
AnyStr是一个字符串和字节类型的特殊类型变量AnyStr = TypeVar('AnyStr', str, bytes)
,它用于可以接受任何类型的字符串而不允许不同类型的字符串混合的函数。
from typing import AnyStr def concat(a:AnyStr,b:AnyStr): return a,b print(concat("aaa",b"aaa"))
from typing import NoReturn def stop() -> NoReturn: print("aaa") stop()
特殊类型,表名没有任何限制;
Any
兼容。Any
对每一个类型都兼容。Any
是一种特殊的类型。静态类型检查器将所有类型视为与Any
兼容,反之亦然, Any
也与所有类型相兼容。
这意味着可对类型为 Any
的值执行任何操作或者方法调用并将其赋值给任意变量。如下所示,将 Any
类型的值赋值给另一个更具体的类型时,Python不会执行类型检查。例如,当把 a
赋值给 s
时,即使 s
被声明为 str
类型,在运行时接收到的是 int
值,静态类型检查器也不会报错
from typing import Any s:Any = "AA" print(s)
总结:typing 模块目前使用的不多,一些基本类型直接就使用,而且类型检查一般只在暴露给外部模块调用的方式的时候才会去设置类型,提高代码的可读性;静态语法的使用,使得Python 的语法结构更接近 Java 一些,可以更好的在大型项目中更加清晰的对变量进行处理;
天行健,君子以自强不息;地势坤,君子以厚德载物;