数据类创建与使用
前言
很多时候我们经常需要处理一条包含多个字段的数据,例如用户(name
, age
, sex
),在将它们写入文件或数据库之前,你会怎么处理它们的存储?最开始的时候,我采用元组和列表,不同索引位置的值代表不同的字段,并将它们存储在一个总的列表中,像下面这样:
datas = [
('Bob', 12, 'man'),
('Alice', 15, 'women'),
...
]
但是很快发现这样结构化的数据可读性并不高,一旦数据字段很多,取出一条数据以后你往往不知道每个位置代表什么。因此,我改用了dict
,新的数据如下:
datas = [
{
'name': 'Bob',
'age': 12,
'sex': 'man'
},
{
'name': 'Alice',
'age': 15,
'sex': 'women'
}
]
的确提高了可读性,并且在数据交付的时候dict
与json
可以无缝衔接,这样传输数据给别人也很方便。但新的问题在于,dict
这个数据结构没有明确指出来这个数据是什么。你只看到:
{
'name': 'Bob',
'age': 12,
'sex': 'man'
}
但他是什么数据类型呢?换句话说,就是缺少一个标签,指明这个数据,一旦我们有多个数据交叉使用,同时需要做类型验证,如用户数据和商品数据在一个管道中,做鉴别筛选的时候,dict
就显得力不从心了。由此,数据类就派上用场了。
namedtuple
光听名字就知道,这是一个有了名字的元组,它可以同时兼顾元组和字典两者的特性,实现高效的数据存储。
定义与初始化
from collections import namedtuple
obj = namedtuple(typename, field_names, verbose=False, rename=False)
- typename:元组名称(可以理解为数据类类名)
- field_names:元组中元素(字段)的名称,有两种传入方式
- 空格隔开的字符串,e.g.
"name age sex"
- 逗号隔开的序列(推荐),e.g.
['name','age', 'sex']
- 空格隔开的字符串,e.g.
- rename:如果元素名称中含有 python 的关键字,则必须设置为 rename=True
- verbose:默认就好
基本使用
下面的代码给出了namedtuple
的基础使用,同时在每一步添加了相应的注释(包括作用和好处),建议仔细阅读
from collections import namedtuple
User = namedtuple('User', ['name', 'sex', 'age']) # 这一步相当于定义了一个User类
user = User(name='Bob', sex='man', age=12) # 实例化对象
print( user._fields ) # 获取所有字段名
# 也可以通过一个序列来创建一个User对象,通过_make方法
user = User._make(['Bob', 'man', 12]) # 这个接口的好处在于可以无缝转换序列数据,这比dict要方便
# 获取用户的属性,这种属性的方式相比于`ditc["name"]`,我个人更喜欢一些
print(user.name)
print(user.sex)
print(user.age)
# 修改对象属性,通过_replace方法,如果你非要修改字段内容,记得要返回值,namedtuple本身是不可变的,这是创建了一个新的
user = user._replace(age=22)
# 将User对象转换成字典,通过_asdict方法,实现了和dict之间的无缝衔接
print(user._asdict()) # 新的版本中已经把ordereddict和dict合并了,因此直接返回dict
- 多数据处理
在实际情况中,我们很可能拿到的是一大堆列表数据,现在要基于namedtuple
进行转换
from collections import namedtuple
users = [
('Bob','man',12),
('Alice', 'women', 15),
('CP', 'superman', 250)
]
User = namedtuple('User',['name','sex','age'])
for user in users:
user = User._make(user)
print(user)
在一般的数据处理中,
namedtuple
已经能够很好地满足基本的数据需求了,但简洁是它的特性,也是它的软肋,例如无法对输入数据类型、数据取值范围进行校验,无法指定可选字段等,因此,我们还需要更awesome的数据类来处理
dataclass & pydantic
dataclass
一听名字就知道是专门的数据类,在python3.7
的时候被加入到标准库中,使用的时候通过from dataclasses import dataclass
即可,虽然它很方便,但由于python3.7
之后版本的限制,这里我直接讲一个与之类似的专门的数据类库pydantic
(python3.6+
)
- 安装
pip install pydantic
基本使用
from typing import Optional
from pydantic import validator
from pydantic.dataclasses import dataclass
from pydantic import BaseModel
# class User(BaseModel): # 也可以通过继承BaseModel的方式初始化
@dataclass(frozen=True) # frozen为True表示数据初始化后不可更改
class User:
name: str # 指定数据类型
age: int
sex: str = 'man' # 设置默认值
address: Optional[str] = None # 可选字段类型
@validator("age")
def age_value_range(cls, v):
if not (0 <= v <= 200):
# 年龄不在合理范围内
raise ValueError("Age can not be set out of [0,200]")
return v
user1 = User(name='GentleCP', age=15, sex='man')
print(user1.name) # GentleCP
user2 = User(name='CP', age=100, sex='man', address='Beijing')
上面的一个例子基本解释了pydantic
的基础使用,但pydantic
作为一个强大的数据类接口,自然还有更多的特性,因为很多内容我们实际上也用不到,这里我仅列举常见可能会使用的,如果感兴趣的可以自己去官网深入了解。
这里要特别说明一下,虽然
BaseModel
和dataclass
两种方式十分相似,但功能上还是存在差异的,如继承BaseModel
的类拥有很多Model
属性,如dict(),json(),parse_obj()
等,但dataclass
是没有的,它只包含必要的数据类型和对数据的验证操作,因此,如果你希望对你的数据有更好的操作,我建议选用BaseModel
基本数据类型
用于初始化的时候指定数据的类型,常用的如下:
from pydantic import BaseModel
from typing import Dict, List, Sequence, Set, Tuple, Union
class Demo(BaseModel):
a: int # 整型
b: float # 浮点型
c: str # 字符串
d: bool # 布尔型
e: List[int] # 整型列表
f: Dict[str, int] # 字典型,key为str,value为int
g: Set[int] # 集合
h: Tuple[str, int] # 元组
i: Union[str, int] # 可以是str或int
字典互转
以下操作基于继承
BaseModel
- 传入字典初始化数据
d = {
'name' : 'CP',
'age' : 15,
'sex' : 'man'
}
user = User(**d)
# user = User.parse_obj(d) # 直接传入字典
# user = User.parse_raw(str(d)) # 解析字符串字典
- 数据转换成字典
user.dict()
# user.josn() # 转换成json数据
总结
本文主要讲解了python
中对数据类的处理,从最开始简单的元组、列表,到更具结构化的字典,再到namedtuple
和pydantic
,虽然它们一个比一个更强大,但并不意味着所有的场景中都要使用某一个,更好的方案是根据自己的需求去使用,例如如果只是简单的2-3个字段的数据,实际上并没有必要大费周章,直接用一个元组或列表即可,但如果一个数据包含多个字段,且本身具有特殊含义,这时候就要考虑namedtuple
或pydantic
了。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!