Python中的泛型
不知道从什么时候起,我写Python代码习惯给变量加上类型注解。这虽然降低了Python的灵活性,但确确实实在后续维护的过程中提供了很好的帮助。
第一次听到泛型这个词,是在Java中了解到的,但具体做什么,有什么作用,都是比较模糊的。因为我本身对Java只是了解并且也没有用Java做过什么实际项目,所以一直对泛型不太明白。
在官方文档中描述了泛型的使用方法,我将从小例子开始,看看泛型是如何帮助我们的。
类型提示
当前有一个get
函数,它会返回一个int
类型的值
def get():
return 1
这样一个函数,我们很快就写完了,而等其他人来调用这个函数时,他就不知道这个get
返回值究竟是什么数据类型。
在Python3.4版本之后,支持类型提示。
我们可以为变量增加类型注解,虽然这个类型注解并不能对强制约束作用,但这可以帮助开发人员更好的维护代码,类型提示使得Pycharm、vscode这样的开发工具更加智能。
def get() -> int:
return 1
int
属于基础数据类型,我们也可以自定义类,例如:
class My(object):
pass
def get() -> My:
return My()
get()
了解标准库typing
https://docs.python.org/zh-cn/3/library/typing.html#
还是以上面的get
函数举例,现在我想让其返回值的类型可能是int
,也可能是My
的实例。
此时,就可用到typing库了。
Union
类型,表示 或
例如下方的例子就是,get
返回值的就是[My, int]
中的任意一个。
from typing import Union
def get() -> Union[My, int]:
return My()
在3.10版本后,支持更加精简的写法:
My | int
初见泛型
为后续讲解泛型,在typing库中,我拿出List
来讲讲。
这是定义了一个列表,但是我怎么知道这个容器内装的是什么玩意呢?
my_list = []
ok,那我提前规定好嘛,my_list
里面全装的My
的实例对象,那就可以这样写:
from typing import List
my_list: List[My] = []
不仅如此,我们还可以让List
装其他东西,例如:
a: List[int] = [] # 声明内部全是int
b: List[str] = [] # 声明内部全是str
c: List[Model] = [] # 声明内部全是Model类的对象
这么能装的吗?
小试泛型
为了更好的理解泛型的作用,我们基于list
来实现一个简单的MyList
。
class MyList(object):
def __init__(self, val: list):
self.list: list = val
def __getitem__(self, item) -> int:
return self.list[item]
a: MyList[int] = MyList([1])
b = a[0] # 调用__getitem__方法
编辑器提示,b
的数据类型是int
,这是因为指定了__getitem__
返回值类型是int
.
现在换成str
c: MyList[str] = MyList(["xy"])
Pycharm还是提示为int
类型,因为我们把__getitem__
返回值类型写死了,所以这里依然返回的int
类型,和我们所设想不一致。
接下来,就让泛型出场吧。
其实,在当前这个需求下,我们只是想__getitem__
返回值的类型随着外界的变化而变化。
这个外界指的就是 MyList[类型]
,[]
中的内容。
使用泛型,我们会经常使用到typing.Generic
和typing.TypeVar
- 声明通用数据类型
所谓泛型,就是通用的数据类型,我们通过typing.TypeVar
先定义一个通用的数据类型.
MyType = TypeVar("MyType", int, str)
str, int
都属于这个通用数据类型下,类型名称是MyType
- 使用泛型
使用这个通用数据类型也很简单,我们只需要在原本的类上继承至Generic[MyType]
即可。
class MyList(object, Generic[MyType]):
def __init__(self, val: list):
self.list: list = val
def __getitem__(self, item) -> MyType:
return self.list[item]
__getitem__
返回值类型就可以设置为MyType
了。
看看效果,这里提示d
的类型是MyType
。
然后,也可以看到pycharm有str
类型的方法提示了,如图:
泛型示例
在写Web应用时,我们会经常写crud,这一小节就演示在实际项目中如何应用泛型。
本节示例代码来自于FastAPI示例项目 https://github.com/tiangolo/full-stack-fastapi-postgresql
定义两个模型,此处用到了pydantic
这个第三方库。
用户类
class User(BaseModel):
id: int
username: str
password: str
email: str
create_time: datetime
update_time: datetime
文章类
class Post(BaseModel):
id: int
title: str
content: str
owner: int
create_time: datetime
update_time: datetime
CURD基类
class CRUDBase(object):
def create(self, obj: CreateSchema) -> ???:
pass
其他类继承至CRUDBase
,例如,我要编写针对于User
的CRUD
类,就可以像这样:
class CRUDUser(CRUDBase):
pass
对于create
这样最基础的方法,在子类中只有很少情况会进行重写。
所以,完全可以在CRUDBase
中实现好,子类就无需再实现了。
关键点就在于create
接受的参数,在本例中,接受了一个CreateSchema
,你可以理解为这是个专门用于创建的数据结构。
例如:
对于User的Schema是:
class UserCreateSchema(BaseModel):
username: str
password: str
email: str
create_time: datetime
对于文章类Post的Schema是:
class PostCreateSchema(BaseModel):
title: str
content: str
create_time: str
owner: int
对于CRUD
的create
方法而言,它只关心传入的schema
,并将schema
的内容插入数据库。
对于子类create
的返回值类型,我们无法提前预知,所以泛型就登场了。
定义两个泛型
from typing import TypeVar, Generic
from pydantic import BaseModel
ModelType = TypeVar("ModelType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
重写CRUDBase
class CRUDBase(Generic[ModelType, CreateSchemaType]):
def create(self, obj: CreateSchemaType) -> ModelType:
pass
继承CRUDBase
class CRUDUser(CRUDBase[User, UserCreateSchema]):
pass
看看实际效果如何:
-
查看
CRUDUser
和CRUDPost
的create
方法返回值类型。
-
向
crud_post.create
传入的UserCreateSchema
可见在静态检查时,抛出了类型异常。
完整代码
from typing import TypeVar, Generic
from datetime import datetime
from pydantic import BaseModel
ModelType = TypeVar("ModelType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
class User(BaseModel):
id: int
username: str
password: str
email: str
create_time: datetime
update_time: datetime
class UserCreateSchema(BaseModel):
username: str
password: str
email: str
create_time: datetime
class Post(BaseModel):
id: int
title: str
content: str
owner: int
create_time: datetime
update_time: datetime
class PostCreateSchema(BaseModel):
title: str
content: str
create_time: str
owner: int
class CRUDBase(Generic[ModelType, CreateSchemaType]):
def create(self, obj: CreateSchemaType) -> ModelType:
pass
class CRUDUser(CRUDBase[User, UserCreateSchema]):
pass
class CRUDPost(CRUDBase[Post, PostCreateSchema]):
pass
crud_post = CRUDPost()
obj = UserCreateSchema(username="mkdir700",
password="xxx",
email="mkdir700@gmail.com",
create_time=datetime.now())
crud_post.create(obj)
- 1
- 1
-
分享