Python实现JSON生成器和递归下降解释器
Python实现JSON生成器和递归下降解释器
github地址:https://github.com/EStormLynn/Python-JSON-Parser
目标
从零开始写一个JSON的解析器,特征如下:
- 符合标准的JSON解析器和生成器
- 手写递归下降的解释器(recursive descent parser)
- 使用Python语言(2.7)
- 解释器和生成器少于500行
- 使用cProfile完成性能分析和优化
实现内容
- [x] 解析字面量(true false null)
- [x] 解析数字
- [x] 解析字符串
- [x] 解析Unicode
- [x] 解析数组
- [x] 解析对象
- [x] 单元测试
- [x] 生成器
- [x] cProfile性能优化
详细介绍
JSON是什么
JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,参考ecma标准,JSON Data Interchange Format,先看一段JSON的数据格式:
{
"title": "Design Patterns",
"subtitle": "Elements of Reusable Object-Oriented Software",
"author": [
"Erich Gamma",
"Richard Helm",
"Ralph Johnson",
"John Vlissides"
],
"year": 2009,
"weight": 1.8,
"hardcover": true,
"publisher": {
"Company": "Pearson Education",
"Country": "India"
},
"website": null
}
在json的树状结构中
- null: 表示为 null
- boolean: 表示为 true 或 false
- number: 一般的浮点数表示方式,在下一单元详细说明
- string: 表示为 "..."
- array: 表示为 [ ... ]
- object: 表示为 { ... }
实现解释器
es_parser 是一个手写的递归下降解析器(recursive descent parser)。由于 JSON 语法特别简单,可以将分词器(tokenizer)省略,直接检测下一个字符,便可以知道它是哪种类型的值,然后调用相关的分析函数。对于完整的 JSON 语法,跳过空白后,只需检测当前字符:
n ➔ literal
t ➔ true
f ➔ false
" ➔ string
0-9/- ➔ number
[ ➔ array
{ ➔ object
对于json的typevalue和json string编写了这样2个类
class EsValue(object):
__slots__ = ('type', 'num', 'str', 'array', 'obj')
def __init__(self):
self.type = JTYPE_UNKNOW
class context(object):
def __init__(self, jstr):
self.json = list(jstr)
self.pos = 0
以解析多余的空格,制表位,换行为例:
def es_parse_whitespace(context):
if not context.json:
return
pos = 0
while re.compile('[\s]+').match(context.json[pos]):
pos += 1
context.json = context.json[pos:]
解析字面量
字面量包括了false,true,null三种。
def es_parse_literal(context, literal, mytype):
e_value = EsValue()
if ''.join(context.json[context.pos:context.pos + len(literal)]) != literal:
raise MyException("PARSE_STATE_INVALID_VALUE, literal error")
e_value.type = mytype
context.json = context.json[context.pos + len(literal):]
return PARSE_STATE_OK, e_value
def es_parse_value(context, typevalue):
if context.json[context.pos] == 't':
return es_parse_literal(context, "true", JTYPE_TRUE)
if context.json[context.pos] == 'f':
return es_parse_literal(context, "false", JTYPE_FALSE)
if context.json[context.pos] == 'n':
return es_parse_literal(context, "null", JTYPE_NULL)
解析数字
JSON number类型,number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。
JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。
JSON 标准 ECMA-404 采用图的形式表示语法,可以更直观地看到解析时可能经过的路径:

python是一种动态语言,所以es_value中num可以是整数也可以是小数,
class es_value():
def __init__(self, type):
self.type = type
self.num = 0
python对于string类型,可以强制转换成float和int,但是int(string)无法处理科学记数法的情况,所以统一先转成float在转成int
typevalue.num = float(numstr)
if isint:
typevalue.num = int(typevalue.num)
实现的单元测试包含:
def testnum(self):
print("\n------------test number-----------")
self.assertEqual(type(self.parse("24")), type(1))
self.assertEqual(type(self.parse("1e4")), type(10000))
self.assertEqual(type(self.parse("-1.5")), type(-1.5))
self.assertEqual(type(self.parse("1.5e3")), type(1.500))
解析字符串
对于字符串中存在转义字符,在load的时候须要处理转义字符,\u的情况,进行编码成unicode

def es_parse_string(context):
charlist = {
'\\"': '\"',
"\\'": "\'",
"\\b": "\b",
"\\f": "\f",
"\\r": "\r",
"\\n": "\n",
"\\t": "\t",
"\\u": "u",
"\\\\": "\\",
"\\/": "/",
"\\a": "\a",
"\\v": "\v"
}
while context.json[pos] != '"':
# 处理转意字符
if context.json[pos] == '\\':
c = context.json[pos:pos + 2]
if c in charlist:
e_value.str += charlist[c]
else:
e_value.str += ''.join(context.json[pos])
pos += 1
continue
pos += 2
else:
e_value.str += ''.join(context.json[pos])
pos += 1
e_value.type = JTYPE_STRING
context.json = context.json[pos + 1:]
context.pos = 1
if '\u' in e_value.str:
e_value.str = e_value.str.encode('latin-1').decode('unicode_escape')
return PARSE_STATE_OK, e_value
单元测试:
def teststring(self):
print("\n------------test string----------")
self.assertEqual(type(self.parse("\" \\\\line1\\nline2 \"")), type("string")) # input \\ is \
self.assertEqual(type(self.parse("\" abc\\def\"")), type("string"))
self.assertEqual(type(self.parse("\" null\"")), type("string"))
self.assertEqual(type(self.parse("\"hello world!\"")), type("string"))
self.assertEqual(type(self.parse("\" \u751F\u5316\u5371\u673A \"")), type("string"))
es_dumps函数,json生成器
将python dict结构dumps成json串
def es_dumps(obj):
obj_str = ""
if isinstance(obj, bool):
if obj is True:
obj_str += "True"
else:
obj_str += "False"
elif obj is None:
obj_str += "null"
elif isinstance(obj, basestring):
for ch in obj.decode('utf-8'):
if u'\u4e00' <= ch <= u'\u9fff':
obj_str += "\"" + repr(obj.decode('UTF-8')) + "\""
break
else:
obj_str += "\"" + obj + "\""
elif isinstance(obj, list):
obj_str += '['
if len(obj):
for i in obj:
obj_str += es_dumps(i) + ", "
obj_str = obj_str[:-2]
obj_str += ']'
elif isinstance(obj, int) or isinstance(obj, float): # number
obj_str += str(obj)
elif isinstance(obj, dict):
obj_str += '{'
if len(obj):
for (k, v) in obj.items():
obj_str += es_dumps(k) + ": "
obj_str += es_dumps(v) + ", "
obj_str = obj_str[:-2]
obj_str += '}'
return obj_str
cProfile性能分析
导入cProfile模块进行性能分析,load中国34个省份地区人口发布,
import cProfile
from jsonparser import *
import json
cProfile.run("print(es_load(\"china.json\"))")
修改部分代码使用python build-in,优化context结构,string在copy的时候比list性能显著提高。消耗时间从20s降到1s



Python实现JSON生成器和递归下降解释器的更多相关文章
- Python 迭代器、生成器、递归、正则表达式 (四)
一.迭代器&生成器 1.迭代器仅仅是一容器对象,它实现了迭代器协议.它有两个基本方法: 1)next 方法 返回容器的下一个元素 2)_iter_方法 返回迭代器自身.迭代器可以使用内建的it ...
- Python技法:实现简单的递归下降Parser
1. 算术运算表达式求值 在上一篇博文<Python技法:用re模块实现简易tokenizer>中,我们介绍了用正则表达式来匹配对应的模式,以实现简单的分词器.然而,正则表达式不是万能的, ...
- Python入门之三元表达式\列表推导式\生成器表达式\递归匿名函数\内置函数
本章目录: 一.三元表达式.列表推导式.生成器表达式 二.递归调用和二分法 三.匿名函数 四.内置函数 ================================================ ...
- python高级之生成器&迭代器
python高级之生成器&迭代器 本机内容 概念梳理 容器 可迭代对象 迭代器 for循环内部实现 生成器 1.概念梳理 容器(container):多个元素组织在一起的数据结构 可迭代对象( ...
- 第三篇:python高级之生成器&迭代器
python高级之生成器&迭代器 python高级之生成器&迭代器 本机内容 概念梳理 容器 可迭代对象 迭代器 for循环内部实现 生成器 1.概念梳理 容器(container ...
- python中的生成器函数是如何工作的?
以下内容基于python3.4 1. python中的普通函数是怎么运行的? 当一个python函数在执行时,它会在相应的python栈帧上运行,栈帧表示程序运行时函数调用栈中的某一帧.想要获得某个函 ...
- Python三大器之生成器
Python三大器之生成器 生成器初识 什么是生成器 生成器本身属于迭代器.继承了迭代器的特性,惰性求值,占用内存空间极小. 为什么要有生成器 我们想使用迭代器本身惰性求值的特点创建出一个可以容纳百万 ...
- Python 迭代器和生成器(转)
Python 迭代器和生成器 在Python中,很多对象都是可以通过for语句来直接遍历的,例如list.string.dict等等,这些对象都可以被称为可迭代对象.至于说哪些对象是可以被迭代访问的, ...
- Python入门篇-生成器函数
Python入门篇-生成器函数 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.生成器概述 1>.生成器generator 生成器指的是生成器对象,可以由生成器表达式得到, ...
随机推荐
- 学习笔记:CentOS7学习之十五: RAID磁盘阵列的原理与搭建
目录 学习笔记:CentOS7学习之十五: RAID磁盘阵列的原理与搭建 14.1 RAID概念 14.1.1 RAID几种常见的类型 14.1.2 RAID-0工作原理 14.1.3 RAID-1工 ...
- js函数(4)闭包
8.6闭包 背景:3.10 变量作用域 在函数体内,局部变量的优先级高于同名的全局变量.如果在函数内声明一个局部变量或者函数参数中带有的变量和全局变量重名,则局部变量会覆盖全局变量: 在全局作用域编写 ...
- DOS命令学习
DOS命令学习 一.DOS使用常识 DOS的概况 DOS(Disk Operating System)是一个使用得十分广泛的磁盘操作系统,就连眼下流行的Windows9x/ME系统都是以它为基础. 常 ...
- oracle建表字段包含关键字注意事项
SQL建表时最好不要把表定义成关键字字段 1 若不小心把表定义成关键字了,比如option, desc等等,oracle版本查询会有问题,短期内无法修改字段名称的,暂时用字段名+双引号解决: SELE ...
- POJ 1177 矩形周长并 模板
Picture 题目链接 http://poj.org/problem?id=1177 Description A number of rectangular posters, photographs ...
- Pangu and Stones(HihoCoder-1636)(17北京OL)【区间DP】
题意:有n堆石头,盘古每次可以选择连续的x堆合并,所需时间为x堆石头的数量之和,x∈[l,r],现在要求,能否将石头合并成一堆,如果能,最短时间是多少. 思路:(参考了ACM算法日常)DP[i][j] ...
- 数据分析—win7+ipython+notebook安装
先安装python 3.x 然后 cmd 执行 pip3 ipython 然后 cmd 执行 pip3 install jupyter notebook 然后 cmd 执行 jupyter noteb ...
- 【Python基础】05_Python中的while循环
1.程序的三大流程介绍 顺序 —— 从上到下,顺序执行 分支 —— 根据条件判断,决定代码的分支 循环 —— 让特定代码执行 2.while 基本语法 while 条件(判断 计数器 是否达到 目标次 ...
- Python第三方库资源
[转载]Python第三方库资源 转自:https://weibo.com/ttarticle/p/show?id=2309404129469920071093 参考:https://github ...
- 5分钟搞定图片鉴黄web应用!
函数工作流(FunctionGraph,FGS)是一项基于事件驱动的函数托管计算服务,托管函数具备以毫秒级弹性伸缩.免运维.高可靠的方式运行.通过函数工作流,开发者无需配置和管理服务器,只需关注业务逻 ...