Python3类型提示
本文环境基于 Python3.6.4。 通过 EPE483和EPE484两个版本,从Python3.5 开始支持类型提示(Type Hint)。
简单的例子
代码1-1: Python3.5之前方法定义
def say_hi(name):
return "Hi,"+name
代码1-2:Python3.5之后的方法定义
def say_hi(name:str)->str:
return "Hi,"+name
Python 与 动态语言
Python作为动态语言, 其设计本意体现了简洁的哲学,这一点从import this
就可以看出。
代码2-1 Python之禅
> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
所谓动态语言,从维基百科上来看,
一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。
而动态类型语言的最大特点也就是只会在运行期间检查数据类型,不用给变量特定的类型,最具代表性的也就是Javascript,Ruby,Python了。
代码2-2
def quack(param):
return param+' is quacking'
上述代码只会在运行期间,Cython解释器做类型检查。
静态类型语言会在运行前(比如:编译期间)判断数据类型,例如Java,C#。
代码2-3
private String quack(String param){
return param+' is quacking';
}
同样上述Java代码会在编译时就会对参数和返回值做检查,如果不一致,则会导致编译错误。
由以上综述可见,我们解释为什么Python设计原则Simple is better than complex.
。动态类型语言没有强制类型检查保证了Python语法的简洁,与此同时动态语言在运行时的类型检查也是影响Python运行效率的一个因素,也给IDE做类型检查时产生了挑战。
而动态语言典型的风格便是鸭子类型。
鸭子类型(duck typing)
鸭子类型(Duck Typing),是动态类型语言的一种风格。“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”在这种风格中,对象不取决于继承的类或是实现的接口,而是关注对象可以调用的方法或是属性。
代码3-1 鸭子类型
# 鸭子类型
class Animal(object):
def run(self):
print("Animal is running")
class Dog(object):
def run(self):
print("Dog is running")
class Cat(object):
def run(self):
print("Cat is running")
def in_the_forest(param):
param.run()
def duck_type():
animal = Animal()
dog = Dog()
cat = Cat()
in_the_forest(animal)
in_the_forest(dog)
in_the_forest(cat)
if __name__ == '__main__':
duck_type()
代码3-1所示,对象animal
,dog
,cat
没有实现或者继承关系,都拥有run
方法,在in_the_forest
方法中分别传入animal
,dog
,cat
对象,调用run方法,各个对象分别表现出不同的状态。
代码3-2 鸭子类型实现多态
# 动态语言鸭子类型来实现多态
class Animal(object):
def run(self):
print("Animal is running")
class Dog(Animal):
# 重写run 方法
def run(self):
print("Dog is running")
class Cat(Animal):
# 重写run 方法
def run(self):
print("Cat is running")
def in_the_forest(animal):
animal.run()
def duck_type():
"""鸭子类型"""
animal = Animal()
dog = Dog()
cat = Cat()
in_the_forest(animal)
in_the_forest(dog)
in_the_forest(cat)
duck_type()
与代码3-1 对比,我们抽象出Animal
类,Dog
类和Cat
类继承Animal
,分别重写run方法,在in_the_forest
方法中通过传入父类Animal
来调用run方法。熟悉Java的同学一定可以看出这是Java中多态,其满足三个必要条件,继承,重写,父类引用指向子类,所以代码3-2 用鸭子类型来实现多态。代码3-3 所示,用Java代码实现上述功能。
代码3-3 Java模拟鸭子类型
// Java通过接口实现来模拟鸭子类型
interface Animal{
default void run(){
System.out.println("Animal is running");
}
}
public class DuckTyping {
static class Dog implements Animal{
@Override
public void run() {
System.out.println("Dog is runnning");
}
}
static class Cat implements Animal{
@Override
public void run() {
System.out.println("Cat is running");
}
}
private static <T extand Animal> void inTheForest(T animal){
animal.run();
}
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
inTheForest(dog);
inTheForest(cat);
}
}
Animal dog = new Dog();
这段代码中dog对象,在编译期表现的是Animal类型,在运行期表现的是Dog类型,而Python在运行期之前并不会表现出任何类型,所以代码3-2使用鸭子类型来实现多态显得鸡肋,还不如直接使用鸭子类型(代码3-1)来得简洁明了。
代码3-3,我们可以做以下修改,通过反射推断入参对象是否存在可调用的方法,使得更加符合鸭子类型,
代码3-4
private static void inTheForest(Object object)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
object.getClass().getMethod("run",new Class<?>[]{}).invoke(object);
}
但是我们再也无法判断inTheForest
中参数object类型,也很难对入参作出约束。所以说鸭子类型放弃类型检查,完全依赖程序员通过文档,注释来弹性约束。
使用
通过 EPE483和EPE484两个版本建议,从Python3.5 开始支持类型提示(Type Hint)。我们可以在typing
包中找到所需的类型。
1. 基本用法
def say_greeting(name:str)->str:
return "Hi,"+name
say_greeting("jian")
2. 类型别名
from typing import List
Vector = List[float] # 定义List别名
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
print(scale(0.5, [1.3, 1.2, 1.2, 1.0]))
3.List,Dict,Tuple
from typing import Dict, Any, Tuple, List
MyDict = Dict[str, Any]
MyTuple = Tuple[str, int]
MyList = List[str]
def show_dict(my_tuple: MyTuple, my_dict: MyDict, my_list: MyList) -> None:
assert (isinstance(my_dict, dict))
assert (isinstance(my_tuple, tuple))
print(my_dict)
print(my_tuple)
show_dict(my_tuple=("1", 1), my_dict={'1': 1}, my_list=["1", "2"])
4. NewType
from typing import NewType
UserId = NewType("UserId", int)
user_a = UserId(121)
user_b = UserId(2112)
print(user_a + user_b)
5. Callable
from typing import Callable
import requests
def async_request(url: str, on_success: Callable[[str], None]) -> None:
request = requests.get(url=url, verify=False)
if request.status_code == 200:
on_success(request.text)
async_request(url='https://www.baidu.com', on_success=lambda result: print("request_call:%s", result))
6. Generics(泛型),运行期间使用isinstance(t,T)
来判断类型,会抛出TypeError
错误。同样issubclass()
也不用。
from typing import TypeVar, List
T = TypeVar('T', int, float) # T 类型必须为int或者float
def sum_from_list(li: List[T]) -> T:
return sum([x for x in li])
print(sum_from_list([1.1, 2, 3, 43, 43, 4]))
7. 泛型约束
from typing import Callable,TypeVar
from requests import Session
from requests import models
T = TypeVar('T',bound=models.Response)
def async_request(url: str, on_success: Callable[[T], None]) -> None:
response = Session().get(url=url, verify=False)
if response.status_code == 200:
on_success(response)
async_request(url='https://www.baidu.com', on_success=lambda response: print("request_call:%s", response.text))
8. typing.TypeVar
T = TypeVar('T') # 任意类型
A = TypeVar('A', str, bytes) # 必须为str,bytes
9. AnyStr ,等价于AnyStr = TypeVar('AnyStr',str,bytes)
10. Union,Union[X, Y] 代表要不是X,要不是Y。
from typing import Union
assert Union[int,str] ==Union [int]#True
assert Union[Union[int, str], float] == Union[int, str, float] #True
assert Union[int] == int # True
assert Union[str, int] == Union[int, str] #True
11. Any,默认任意类型
from typing import Any
def show_any(param: Any) -> Any:
return 1, param, True
a, b, c = show_any(param='any_param')
print(a, b, c)
12.Optional,可选择类型
from typing import Optional,List
xs: List[Optional[str]] = []
#等价于 xs = []
....
参考:https://docs.python.org/3/library/typing.html