glom
是一种用Python处理数据的新方法,具有以下特点:
- 嵌套结构并基于路径访问
- 使用轻量级的Pythonic规范进行声明性数据转换
- 可读、有意义的错误信息
- 内置数据探测和调试功能
- …
接下来,将记录一下glom
的使用:
安装
pip3 install glom
举个栗子
假设有这样一个数据,结构如下:
d = {"a": {"b": {"c": 1}}}
此时,我们可以使用glom
来获取到最里面c
所对应的值,用法如下:
from glom import glom
d = {"a": {"b": {"c": 1}}}
print(glom(d, "a.b.c")) # 1
通常来说,我们获取这种结构的数据,都是采用如下方式:
print(d["a"]["b"]["c"]) # 1
从而获取到数据。这看起来也是非常简单的对吧,但是通常情况下,不会这么顺利,也有可能会遇到空值得情况,如下:
d = {"a": {"b": None}}
print(d["a"]["b"]["c"])
Traceback (most recent call last):
...
print(d["a"]["b"]["c"])
TypeError: 'NoneType' object is not subscriptable
我们得到了一个错误提示,但是这对我们而言没有任何作用,错误消息甚至没有告诉我们哪个访问失败。对于这种情况,我们再来看一下glom
的处理方式,如下:
from glom import glom
d = {"a": {"b": None}}
print(glom(d, "a.b.c"))
Traceback (most recent call last):
...
glom.core.PathAccessError: could not access 'c', part 2 of Path('a', 'b', 'c'), got error: AttributeError("'NoneType' object has no attribute 'c'",)
如此错误提示还算差不多,我们可以阅读,理解,并采取行动!
超越访问
首先,我们来看一下glom
方法:
glom(target, spec, **kwargs)
首先,我们来介绍一下这几个参数:
- target:我们的数据,可以是dict、list或者其他任何对象
- spec:是我们希望输出的内容
现在我们一组特殊的数据,我们希望通过glom
来将它们获取出来,数据如下:
data = {"student": {"info": [{"name": "laozhang"}, {"name": "laowang"}]}}
现在我们希望能搞列出所有name
的数据,我们可以通过glom
来实现,代码如下:
from glom import glom
info = glom(data, ("student.info", ["name"]))
print(info) # ['laozhang', 'laowang'], 为一个list
此时,我们不想用一个列表来接收他们,我希望通过一个字典,并赋予一个键来接收,如下:
from glom import glom
info = glom(data, {"info": ("student.info", ["name"])})
print(info) # {'info': ['laozhang', 'laowang']}, 为一个dict
改变要求
不幸的是,现实中的数据很混乱,你可能期待某种格式,结果却得到了完全不同的结果。例如有这样两组数据:
from glom import glom, Coalesce
data_1 = {"school": {"student": [{"name": "laozhang"}, {"name": "laowang"}]}}
data_2 = {"school": {"teacher": [{"name": "张老师"}, {"name": "王老师"}]}}
spec = {"name": (Coalesce("school.student", "school.teacher"), ["name"])}
print(glom(data_1, spec)) # {'name': ['laozhang', 'laowang']}
print(glom(data_2, spec)) # {'name': ['张老师', '王老师']}
Coalesce
是一个glom
结构,允许你为你的子目录指定回退行为。但是,如果有一组数据,同时满足Coalesce
中的所有匹配,那么它将会依次匹配,直到满足匹配为止。即在上面的栗子中,如果数据同事匹配school.student
和school.teacher
,会取出匹配的school.student
的数据,而不会往下匹配了!!!
Python工具
大多数其他实现都局限于特定的数据格式或纯模型,无论是jmespath
还是Xpath/XSLT
,glom
没有牺牲其实用性,而是充分利用Python本身的强大功能。假设有这样的数据:
data = {"school": {"student": [{"name": "laozhang", "age": 18}, {"name": "laowang", "age": 20}]}}
然后,我们需要统计所有age的和,此时,我们可以使用glom
这样操作:
from glom import glom
spec = {"sum_age": ("school.student", ["age"], sum)}
print(glom(data, spec)) # {"sum_age": 38}
联系点
glom是一种实用的生产工具,为了更好的演示如何使用它,我们将构建一个API响应。我们正在实现一个联系人Web页面,如地址簿,但是需要一个ORM/数据库,并且兼容Web和移动前端。下面,我们创建一个联系人模型:
from datetime import datetime
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine("mysql+pymysql://root:[email protected]/test?charset=utf8")
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(20))
age = Column(Integer)
create_time = Column(DateTime)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
u1 = User(name="laowang", age=20, create_time=datetime.now())
u2 = User(name="laozhang", age=18, create_time=datetime.now())
session.add_all([u1, u2])
session.commit()
然后,我们可以使用ORM来获取数据:
data = session.query(User).all()
接下来我们设置指定结果的格式,而后使用glom
进行处理,代码如下:
spec = {
"result": [{
"user_id": "id",
"user_name": "name",
"user_age": "age",
"create_time": "create_time"
}]
}
print(glom(data, spec))
############## 结果如下 ##############
{
'result': [{
'id': 1,
'name': 'laozhang',
'age': 18,
'create_time': datetime.datetime(2018, 9, 19, 10, 8, 19)
}, {
'id': 2,
'name': 'laowang',
'age': 20,
'create_time': datetime.datetime(2018, 9, 19, 10, 8, 28)
}]
}
从spec
的格式可以看出,键可以任何有意义的名称,而值只可以为ORM中对应的类属性名称!!至此,Over~~~