目录
前言
1、typing是干什么的?
2、typing模块的作用:
3、typing中各种数据类型的使用
3.1 最简单的 int、str、LIst、Dict
3.2 Dict、Tuple、Sequece,Mapping
3.3 Callable
3.4 标记自定义的
3.5 泛型
3.5.1 泛型在函数中的使用方法
3.5.2 自定义的泛型类
最近在项目中使用fastapi框架编写后台接口时,用到了schema,而schema是用pydantic来进行验证的。pydantic中的数据验证使用到了typing中的很多模块,因此开始注意到这个typing。在网上查资料,并没有太多有价值的信息,基本上都是从python document中复制粘贴过来的。
让人恼火的是,python document 对于 typing这个模块的使用的讲解,让人很难懂。因此特写此文,结合自己使用上的理解,来记录一下typing模块到底是怎么使用的。
在typing模块的意义上,python document的介绍还是很清晰的,建议大家去看一下 PEP 484 -- Type Hints,这里面介绍的typing的由来,以及typing出现前,大家是怎样通过注释来解决注释的问题的,以及python这个动态语言为什么需要typing等等内容。相信看完后,可以让大家非常深刻理解 typing出现的意义是什么。下面附上链接:
https://www.python.org/dev/peps/pep-0484/#abstract
这篇文章我会基于 python document中的给出的例子,着重分析每一个例子是什么意思,比如说官网给出的泛型的定义方法,但是没有说明是怎样去使用的,而这篇文章就是对其的一个补充,详细的讲怎么用。
这个话题,如果大家有时间,建议还是去看 PEP 484 ,会给你讲 typing的前生今世,非常好的内容。如果没时间,或者图省事,那就看我下面的介绍吧。
首先,Python是解释型语言,不会在程序执行前对代码进行检查。python又是动态语言,动态类型带来的直观表现有两点。第一,在创建变量时无需指定数据类型;第二,可以将不同类型的值赋给同一变量。
我们知道,项目变大变复杂的时候,动态类型的缺点就会变得明显,没有完善的代码提示,代码可读性差等缺点会使代码维护变得困难。
很多人在写完代码一段时间后回过头看代码,很可能忘记了自己写的函数需要传什么参数,返回什么类型的结果,就不得不去阅读代码的具体内容,降低了阅读的速度,加上Python本身就是一门弱类型的语言,这种现象就变得更加的严重,而typing这个模块很好的解决了这个问题。
1、类型检查,防止运行时出现参数和返回值类型不符合。
2、作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
3、该模块加入后并不会影响程序的运行,不会报正式的错误,只有提醒。
4、IDE可以提供更精准的代码提示,提高开发效率
在pycharm中是集成支持typing的,我们测试一下,看看自动提示的效果,见下图
我们可以看到,当我对stu标记为一个list变量时,弱传参时不是list类型时,IDE会提示
注意:typing模块只有在python3.5以上的版本中才可以使用
这几个其实就简单了,也是我们最常见的,所以先说他们,我们就有一个初步的认识了,见下面的例子。
from typing import List, Dict
#def simple_type(a, c, d, b):
# pass
def simple_type(a: int, c: Dict[str:str], d: List, b: str = ""):
pass
我们这里写了一个简单函数,我们通常都会写成 上面被注释掉的 函数的样式。但是我们并不知道a, b, c, d他们要传什么值,只能通过阅读上下文去判断,很不方便。
那我们写成下面的函数的样子,就很好判断了,并且这时候IDE也知道你要传什么值,传值不匹配时会提示你。
那么我们再看看每个参数的含义
a: int 表示a是一个int类型,无默认值
c: Dict[str: str] 表示c是一个字典类型,且key和value均是str类型
d: List 表示d是一个list类型,无默认值
b: str = "" 表示b是一个str类型,默认值为 ""
这几个类型和str、int他们其实没有什么区别。
其中Sequence,表示的是序列,list、tuple、collections.deque、str、bytes的都属于序列
Mapping表示的映射类型,包含dict
def simple_type(a: Sequence, c: Dict[str:str], d: List[int, List], b: Mapping[str: int]):
pass
在typing中还有非常的类型,大家可以去 python document中对typing模块的介绍中去找,对应abc模块中的数据类型,typing有一全套的与其对应。
前面的都是针对普通变量的hint,当然也有针对函数的hint了,这就是Callable。
如果我们想要标记目前的参数为一个可调用的对象,我们就可以使用Callable。
格式为:
Callable[[Arg1Type, Arg2Type], ReturnType]
其中[Arg1Type, Arg2Type]为传递的参数类型
ReturnType为返回参数的类型
如果传递参数和返回参数均没有,直接写Callable即可
如:
from typing import Callable
def feeder(get_next_item: Callable[[], str]) -> None:
# Body
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
ok。现在我们可以表示简单的数据类型、函数类型等。但是如果我新见了一个数据类型,或者说一个类对象,那用什么来标记这个数据类型呢?我们可以直将定义的数据类型最为hint即可。
我们数据库新建了一个项目的表,使用的是project类作为创建表的字段,那么当我们去数据库查出project的条目,并且转化为Projcet对象返回时,我们就可以直接以用Project类来标记返回类型.
当我们想用一种hint对象来动态的表示传入的对象的类型值时,我们就可以使用泛型。
泛型的使用,和我们上面讲到的List、Dict、Sequence等使用方式是一样的。当然我们也可以定义一个类,这个通过继承某个泛型,从而这个类的内部都可以使用这个泛型T。
定义一个泛型变量
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
这个泛型变量,我们可以直接把它当成一个变量,我传入的参数是什么类型,这个T就代表什么类型,后面就可以直接使用这个叫做T的hint。
def first(arg1: Sequence[T]) -> T: # Generic function
return arg1[2]
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
。这也使得 T
作为类体内的一个类型有效。
泛型类型可以有任意数量的类型变量,并且类型变量可能会受到限制:
from typing import TypeVar, Generic
...
T = TypeVar('T') # 代表T这个泛型可以由无限种类型
S = TypeVar('S', int, str) # 代表S这个泛型,只支持int和str
class StrangePair(Generic[T, S]): # 代表SrangePair这个泛型类只支持T和S这两种类型
...
下面针对泛型,举一个具体的例子,这个例子是我在做项目中,项目的框架中用到的代码,涉及泛型。
# 这是base.py文件,使用泛型的形式,可以根据传入的model类型的不同,查询不同数据库
from app.dao.orm.base import Base
Model = Typevar("odel",bound-Base)
Createschema = Typevar("Createschema",bound-BaseModel)
updateschema = TypeVar("updateschema",bound-BaseModel)
class CRUDBase(Generic[Model,Createschema,Updateschema]):
@property
def model_class(self)-> Type[Model]:
return self.orig_bases_[0].args[0]
def create(self,session:Session,attrs:Createschema)-> Model:
model = self.modelclass(**attrs.dict())
session.add(model)
session.flush)
session.refresh(model)
return model
def latest_created(self,session:Session)-> optional[Model]:
rules = self.read_many(session,Limit=1,sort="created",desc,True)
return None if len(rules)< 1 else rules[0]
def read(self,session:Session,id:int) -> Optional[Model]:
return self.read_by(session,"id",id)
# 这是model.py。定义了model的格式,即sqlalchemy创建表的依据
class ProjectCreateAPI (ArtCreateAPI):
creator: str
type = art.ProjectType.credit
code: str
ppm_code: Optional[ str]
organization: str
is_active = True
class ProjectCreate (ArtCreate):
type = art.ProjectType.credit
code: str
ppm_code: Optional [ str]
organization: str
is_active = True
# 这个crud.py,通过传入model的类型给base.py,base.py的CRUDBase定义的是泛型,因此,根据传入的model的类型的不同(这里的model有 项目、模型、文档等几种类型),查询不同的数据表数据
class CRUDProject(CRUDBase [ Project, ProjectCreate, ProjectUpdate]):
def read_user_project(self, session: Session, user: str, name: str) -> Optional[Project]:
return (
session. query(self. model_class).filter (self. model_class. creator == user).first()
)
def read_user_projects(
self, session: Session, user: str, *, skip=o, limit=128, sort="id", desc=False
)-> List[Project]:
return self.read_many(session, self. model_class)
上面这个例子,是CRUD层的base与core中使用的。CRUD的core层目的就是直接调用base层的代码,进行数据库查询。
而对于model这个参数,我们是有 project、model、doc三种类型的,所以为了base能够用一个类接受不同的 model-type,因此使用了泛型类,也就是
class CRUDBase(Generic[Model,Createschema,Updateschema]):
pass
而当我调用它时,根据我想要查寻的model-type,传入类CRUDBase即可,它识别后,会进行针对此model-type的数据库查询。