python高级模块包之typing

2022-10-27 14:15:54

目录

前言

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中的给出的例子,着重分析每一个例子是什么意思,比如说官网给出的泛型的定义方法,但是没有说明是怎样去使用的,而这篇文章就是对其的一个补充,详细的讲怎么用。

1、typing是干什么的?

这个话题,如果大家有时间,建议还是去看 PEP 484 ,会给你讲  typing的前生今世,非常好的内容。如果没时间,或者图省事,那就看我下面的介绍吧。

首先,Python是解释型语言,不会在程序执行前对代码进行检查。python又是动态语言,动态类型带来的直观表现有两点。第一,在创建变量时无需指定数据类型;第二,可以将不同类型的值赋给同一变量。

我们知道,项目变大变复杂的时候,动态类型的缺点就会变得明显,没有完善的代码提示,代码可读性差等缺点会使代码维护变得困难。

很多人在写完代码一段时间后回过头看代码,很可能忘记了自己写的函数需要传什么参数,返回什么类型的结果,就不得不去阅读代码的具体内容,降低了阅读的速度,加上Python本身就是一门弱类型的语言,这种现象就变得更加的严重,而typing这个模块很好的解决了这个问题。

2、typing模块的作用:

1、类型检查,防止运行时出现参数和返回值类型不符合。

2、作为开发文档附加说明,方便使用者调用时传入和返回参数类型。

3、该模块加入后并不会影响程序的运行,不会报正式的错误,只有提醒。

4、IDE可以提供更精准的代码提示,提高开发效率

在pycharm中是集成支持typing的,我们测试一下,看看自动提示的效果,见下图

我们可以看到,当我对stu标记为一个list变量时,弱传参时不是list类型时,IDE会提示

注意:typing模块只有在python3.5以上的版本中才可以使用

3、typing中各种数据类型的使用

3.1 最简单的 int、str、LIst、Dict

这几个其实就简单了,也是我们最常见的,所以先说他们,我们就有一个初步的认识了,见下面的例子。

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类型,默认值为 ""

3.2 Dict、Tuple、Sequece,Mapping

这几个类型和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有一全套的与其对应。

3.3 aliases的使用

这种方式是比较简单的将多个类型组合起来,一起进行hint的。给他们起一个别名,以后就可以直接使用别用来代替了。具体看下面的例子。

from typing import List
Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

# typechecks; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])

3.4 Callable

前面的都是针对普通变量的hint,当然也有针对函数的hint了,这就是Callable。

如果我们想要标记目前的参数为一个可调用的对象,我们就可以使用Callable。

格式为:

Callable[[Arg1Type, Arg2Type], ReturnType]


其中[Arg1Type, Arg2Type]为传递的参数类型(也就是signature的类型)
    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

3.5 标记自定义的

ok。现在我们可以表示简单的数据类型、函数类型等。但是如果我新见了一个数据类型,或者说一个类对象,那用什么来标记这个数据类型呢?我们可以直将定义的数据类型最为hint即可。

我们数据库新建了一个项目的表,使用的是project类作为创建表的字段,那么当我们去数据库查出project的条目,并且转化为Projcet对象返回时,我们就可以直接以用Project类来标记返回类型.

3.6 泛型

当我们想用一种hint对象来动态的表示传入的对象的类型值时,我们就可以使用泛型。

泛型的使用,这个通过继承某个泛型,从而这个类的内部都可以使用这个泛型T。

3.5.1 泛型在函数中的使用方法

泛型包括的 类型容器(TypeVar)和 Generic的使用。我们看看他们是怎么联合起来创建泛型的。

from typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

S = TypeVar("S", str, int)  # 表示S只能是str或者int类型

这个泛型变量,我们可以直接把它当成一个变量,我传入的参数是什么类型,这个T就代表什么类型,后面就可以直接使用这个叫做T的hint。

def first(arg1: Sequence[T]) -> T:   # Generic function
    return arg1[2]

3.5.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的数据库查询。

3.7 typing.Type的用法

接受的参数均为class。表示接受自身或者自己的子类

也是用于hint中。具体看下面的例子

from typing import Type

class Person:
    pass

class User: ...


class BasicUser(User): ...


class ProUser(User): ...


class TeamUser(User): ...


# Accepts User, BasicUser, ProUser, TeamUser, ...
def make_new_user(user_class: Type[User]) -> User:
    # ...
    return user_class()

make_new_user(BasicUser)
make_new_user(ProUser)
make_new_user(User)
make_new_user(TeamUser)
make_new_user(Person)

在ID E上会提示如下,表示Person这个类并不是User的子类

3.8 Union的用法

Union type;Union[X, Y] means either X or Y。也就是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]
Union[int, str] == Union[str, int]
  • 作者:NeverLate_gogogo
  • 原文链接:https://blog.csdn.net/NeverLate_gogogo/article/details/107106715
    更新时间:2022-10-27 14:15:54