第1关:JSON篇:JSON基础知识
任务描述
本关任务:手动编写一个 JSON 格式的数据文件。
相关知识
JSON 全称:JavaScript Object Notation(JavaScript 对象表示法),是一种轻量级的文本数据交换格式。与接下来要介绍的 XML 相比,拥有更小、更快、更易解析的特点。
一个典型的 JSON 格式的字符串如下:
{
"students":
[
{ "name":"赵昊" , "age": 15 },
{ "name":"龙傲天" , "age": 16 },
{ "name":"叶良辰" , "age": 17 }
]
}
可以看到其结构和 Python 的list、dict有点相似。
数据表示
JSON 中数据都以名称:值的形式表示,名称包括在一对双引号" "中,值则有多种形式,多条数据之间用逗号,隔开,比如:
"name":"赵昊"
"name":"赵昊","age":15
这种表示方式与 Python 的dict类似。
数据类型
JSON 的值可以是如下类型:
字符串(在双引号中)
数字(整数或浮点数)
逻辑值(true 或 false)
数组(在中括号中)
对象(在大括号中)
null
比如:
"name":"赵昊" , "age":15 , "height":170.5 , "ismale" : false , "house":null
数组
JSON 的数组用一对方括号[]表示,类似于list,数组元素用逗号,隔开,元素值可以是任意 JSON 数据类型,比如:
"names":["赵昊","龙傲天","叶良辰"]
"ages":[15,16,17]
对象
JSON 的对象用一对大括号{}表示,类似于dict,对象可以拥有多个名称/值 对,名称/值 对用逗号,隔开,比如:
"first":{ "name":"赵昊" , "age": 15 }
编程要求
根据提示,在右文件中补充代码,创建一个 JSON 格式的字符串,创建完成后,记得删除第一行提示语,要求:
返回的 JSON 字符串代表一个对象
对象的属性有:
students:一个数组,包含三个学生对象
count:学生的数量,在这里是3个
学生对象的属性:
名称name,字符串类型
年龄age,number(int)类型
是否为男性ismale,逻辑值类型
三个学生对象的数据:
名称:赵昊,年龄:15,男性
名称:龙傲天,年龄:16,男性
名称:玛丽苏,年龄:15,女性
测试说明
测试代码会解析这个文件内的 JSON 字符串,并将其中的数据打印出来。
如果文件成功解析而且数据填写正确,将会有如下结果输出:
学生数:3
名称:赵昊,年龄:15,男性
名称:龙傲天,年龄:16,男性
名称:玛丽苏,年龄:15,女性
开始你的任务吧,祝你成功!
参考代码:
{
"students":[
{
"name":"赵昊",
"age":15,
"ismale":true
},
{
"name":"龙傲天",
"age":16,
"ismale":true
},
{
"name":"玛丽苏",
"age":15,
"ismale":false
}
],
"count":3
}
第2关:JSON篇:使用json库
任务描述
本关任务:编写一个能读取并修改 JSON 数据的程序。
相关知识
json库是 Python 内置的一个用于操作 JSON 数据的库,提供了几个函数用于解析与生成(或者说反序列化与序列化)JSON 格式的数据。
解析 JSON 数据
json库提供了一个函数loads,用于从 Python 的字符串中解析 JSON 数据。使用它的方法很简单,只需将含有 JSON 数据的字符串当做参数传递给它,它的返回值就是由 Python 中的基础数据类型组成的对象。
import json
data = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
text = json.loads(data)
print(text)
得到的结果是:
{‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4, ‘e’: 5}
JSON 的对象类型转换成了 Python 的dict类型。
JSON 各种数据类型在解析后,对应的 Python 基础数据类型如下表:
JSON 数据类型转化成 Python 数据类型后,就可以按照 Python 的方式来使用了:
import json
data = '[1,2,3]';
text = json.loads(data)
text.append(4) #调用list的append函数
print(text)
得到的结果是:
[1, 2, 3, 4]
如果需要以不同的字符编码来解析,可以指定 encoding参数,比如:
import json
data = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
text = json.loads(data,encoding = "utf-8")
print(text)
上面的代码以utf-8的字符编码,解析data字符串的数据。
注意:如果字符编码指定错误,有可能会导致解析失败,引发异常。
json库的另一个函数load也是用于解析 JSON 数据的,它与loads函数唯一不同的地方在于,它是从文件中解析,比如:
import json
data = open("test.txt","r",encoding = "utf-8")
text = json.load(data) #将文件对象传递给load函数
print(text)
fp.close()
这样test.txt文件内的内容,就会被当做 JSON 格式的数据来解析。
注意:load函数没有可选参数encoding,只要文件对象使用了正确的字符编码打开文件,load函数就可以正确的解析数据。
生成 JSON 数据
与解析的那两个函数相对应,json库也提供了两个函数:dumps和dump,来将由 Python 基础数据类型组成的对象转化成 JSON 数据,使用方法也类似:
import json
data = [ { 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 } ]
json = json.dumps(data) #转化成JSON格式的字符串
print(json)
得到的结果是:
[{“a”: 1, “b”: 2, “c”: 3, “d”: 4, “e”: 5}]
注意,这是一个字符串。
同样,在转化的时候,也有一个 Python 基础数据类型到 JSON 数据类型的对应表格:
注意:dumps没有可选参数encoding,当要转化的对象含有中文等非 ASCII 字符时,建议指定可选参数ensure_ascii为False。否则非 ASCII 的字符将会被显示成\uXXXX的形式:
data = {"name":"小明"}
print(json.dumps(data)) #ensure_ascii默认值为True
print(json.dumps(data,ensure_ascii= False)) #指定ensure_ascii为False
上面的代码的结果:
{“name”: “\u5c0f\u660e”}
{“name”: “小明”}
使用dump函数直接输出到文件也很简单,只需多传递一个文件对象作为参数。
import json
fp = open("test.txt","w")
data = [ { 'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4, 'e' : 5 } ]
json.dump(data,fp) #转化成JSON格式的字符串后输出到文件
fp.close()
test.txt文件的内容:
[{“a”: 1, “b”: 2, “c”: 3, “d”: 4, “e”: 5}]
dump函数同样也没有可选参数encoding,如果有数据中有中文字符等非 ASCII 字符时,建议指定可选参数ensure_ascii为False。
编程要求
根据提示,在右侧编辑器Begin-End区间补充代码,实现以下功能:
有一个字符编码为utf-8的数据文件step2/2017.txt:
{"count":3 ,
"infos":
[
{"name":"赵昊" , "age":15 ,"height": 1.83, "sex" : "男性" },
{"name":"龙傲天" , "age":16 ,"height": 2.00, "sex" : "男性"},
{"name":"玛丽苏" , "age":15 ,"height": 1.78, "sex" : "女性"}
]
}
将其按 JSON 格式解析后,将infos数组内的三个对象的年龄age增加一岁,然后增加一条记录:
名称:叶良辰,年龄:17,身高:1.87,男性
同时,将count的值从3改为4。
注意:所有打开的文件,请在打开文件的函数内及时关闭,以免影响测试代码读取数据。
测试说明
测试代码会以 JSON 格式解析step2/2018.txt的内容,并将其中的数据打印出来。
如果内容成功解析,并且数据填写正确,将会有如下结果输出:
学生数:4
名称:赵昊,年龄:16,身高:1.83,男性
名称:龙傲天,年龄:17,身高:2.00,男性
名称:玛丽苏,年龄:16,身高:1.78,女性
名称:叶良辰,年龄:17,身高:1.87,男性
开始你的任务吧,祝你成功!
参考代码:
import json
def Func():
data = open("step2/2017.txt","r",encoding = "utf-8")
obj = json.load(data)
data.close()
#********** Begin *********#
infos = obj['infos']
for stu in range(len(infos)):
infos[stu]['age']+=1
infos.append({'name':'叶良辰','age':17,'height':1.87,'sex':'男性'})
obj = {
'count':4,
'infos':infos
}
#********** End **********#
output = open("step2/2018.txt","w",encoding = "utf-8")
json.dump(obj,output) #输出到文件
output.close()
第3关:XML篇:XML基础知识
任务描述
本关任务:手动编写一个 XML 格式的数据文件。
相关知识
XML 全称可扩展标记语言(EXtensible Markup Language),是一种用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型。
一个典型的 XML 文件结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
XML 的声明
XML 文档的声明是可选的,如果要声明,需要将其放在文档的第一行最顶端。
<?xml version="1.0" encoding="utf-8"?>
以上定义了 XML 的版本1.0和所使用的编码utf-8。
元素
XML 元素指的是 XML 文件中出现的标签,一个标签分为开始标签和结束标签,结束标签与开始标签名称相同,只是要在前面加一个斜杠/,比如:
<hello></hello>
<to></to>
一个标签中也可以嵌套其他的若干个子标签。但所有的标签必须合理的嵌套,绝不允许交叉嵌套,比如:
<student>
<name>赵昊</name>
<age>16</age>
</student>
以上是一个正确嵌套的例子。
格式良好的 XML 文档必须有且仅有一个根元素,其他的元素都是这个元素的子孙元素。
元素的命名规则:
名称可以包含字母、数字以及其他的字符
名称不能以数字或者标点符号开始
名称不能以字母 xml(或者 XML、Xml 等等)开始
名称不能包含空格
名称是大小写敏感的
XML 没有保留字,元素名称可以任意使用。
<hello></hello>
<F12></F12>
<if></if>
<!--上面这些都是合理的名称-->
<!--下面这些都是不合理的名称-->
<.123></.123>
<A D></A D>
<hello></Hello> <!-- 这一个是因为开始标记(hello)和结束标记(Hello)名称不同 -->
属性
一个元素可以有多个属性,每个属性都有它自己的名称和取值。同一个元素下,属性名称不能重复。属性的值一定要用双引号或者单引号括起来,比如:
<date year="2018" month="7" day="28"></date>
以上是一个格式正确的例子。
属性的命名规范和元素的命名规范一样。
注释
XML 文件中注释采用:<!--注释内容-->
这样的格式,注释没有结束标签,不能嵌套。举例如下:
<!--一个正确的注释1-->
<hello>
<!--一个正确的注释2-->
</hello>
<!--<!--一个发生嵌套的错误的注释-->-->
编程要求
根据提示,在右侧编辑器的文件中手动编写一个 XML 文件,要求:
附带文档声明,使用 XML 版本1.0,字符编码为utf-8;
文档有一个根元素data,根元素有一个属性count,值为3;
根元素有三个student子元素,代表三个学生对象,每个子元素都有三个属性name,age,sex(值为男性或者女性),它们的值由下面的数据得来。
三个学生对象的数据:
名称:赵昊,年龄:15,男性
名称:龙傲天,年龄:16,男性
名称:玛丽苏,年龄:15,女性
提示:代码补充完毕,记得删除第一行提示语。
测试说明
测试代码会以 XML 格式解析这个文件的内容,并将其中的数据打印出来。
如果内容解析成功而且数据填写正确,将会有如下结果输出:
学生数:3
名称:赵昊,年龄:15,男性
名称:龙傲天,年龄:16,男性
名称:玛丽苏,年龄:15,女性
开始你的任务吧,祝你成功!
参考代码:
<?xml version="1.0" encoding="UTF-8"?>
<data count="3">
<student name="赵昊" age="15" sex="男性"></student>
<student name="龙傲天" age="16" sex="男性"></student>
<student name="玛丽苏" age="15" sex="女性"></student>
</data>
第4关:XML篇:使用SAX库解析XML
任务描述
本关任务:解析 XML 文件,输出个人信息。
相关知识
SAX是一种基于事件驱动的 API,使用SAX解析 XML 时,主要分两个部分:解析器和事件处理器。
解析器
解析器负责读取 XML 文档,并向事件处理器发送事件,比如元素开始跟元素结束事件。
SAX提供了两个函数:parse和parseString。前者用于从文件中解析 XML 数据,后者用于从字符串中解析 XML 数据。
两个函数的声明如下:
xml.sax.parse( xmlfile, contenthandler)
xml.sax.parseString(xmlstring, contenthandler)
parse函数的第一个参数是 XML 文件的路径,比如:
xml.sax.parse("test.xml",contenthandler)
parseString函数的第一个参数是,含有 XML 数据的字符串,比如:
data = "<data>Hello</data>"
xml.sax.parseString(data,contenthandler)
而它们的第二个参数则是,接下来要介绍的事件处理器。
事件处理器
事件处理器则负责对事件作出响应,对传递的 XML 数据进行处理。
一个事件处理器必须是ContentHandler类型的子类,通过重写父类的以下函数来响应解析器的事件请求:
cha\fracters(content)函数,它会在以下时机被调用:
如果从一行开始,遇到标签之前,存在字符,则content的值为这些字符串;
如果从一个标签,遇到下一个标签之前,存在字符,则content的值为这些字符串;
如果从一个标签,遇到行结束符(换行符)之前,存在字符,则content的值为这些字符串;
标签可以是开始标签,也可以是结束标签。
startDocument()函数,它在文档启动的时候调用;
endDocument()函数,它在解析器到达文档结尾时调用;
startElement(name, attrs)函数,它在遇到 XML 开始标签时调用,name是标签的名字,attrs是标签的属性值字典;
endElement(name)函数,它在遇到 XML 结束标签时调用。
假设有如下文件test.xml:
<data>文字1
文字2<ele1>元素1数据</ele1>文字3
文字4<ele2>元素2数据</ele2>文字5
</data>
当运行以下代码时:
import xml.sax
class Handler(xml.sax.ContentHandler):
def startDocument(self):
print("文档开始")
def endDocument(self):
print("文档结束")
def startElement(self,name,attrs):
print("开始标签:" , name)
def endElement(self,name):
print("结束标签:" , name)
def cha\fracters(self,content):
print("数据:" ,content)
if __name__ == '__main__':
xml.sax.parse("test.xml",Handler())
会得到以下结果:
文档开始
开始标签: data
数据: 文字1
数据:<换行>
数据: 文字2
开始标签: ele1
数据: 元素1数据
结束标签: ele1
数据: 文字3
数据:<换行>
数据: 文字4
开始标签: ele2
数据: 元素2数据
结束标签: ele2
数据: 文字5
数据:<换行>
结束标签: data
文档结束
注意:结果中的<换行>其实是换行字符\n,为了方便阅读才改成<换行>。
将其与test.xml中数据的结构对比一下,就可以明白每个函数调用的时机了。
编程要求
根据提示,在右侧编辑器补充代码,要求:
GetHandler函数必须要返回一个事件处理器,它将被用于解析个人信息文件data.xml;
这个事件处理器在解析文件的过程中,要能识别出个人信息中个人签名的部分,然后将它打印出来。
data.xml的文件结构见测试说明。
测试说明
data.xml文件的结构:
<data>
<info name="XXX" age="XXX">个人签名部分</info>
....
</data>
测试用的代码也会产生输出,与GetHandler返回的事件处理器的输出结合以后,将会得到以下结果:
赵昊的个人签名:
我赵昊第一个不服!
龙傲天的个人签名:
我命由我不由天
玛丽苏的个人签名:
我冷若冰霜,冷酷无情
叶良辰的个人签名:
良辰日后必有重谢!
其中第1,3,5,7行是测试代码产生的输出(不需要学员实现),2,4,6,8行则是事件处理器应该有的输出。
开始你的任务吧,祝你成功!
参考代码:
import xml.sax
class Handler(xml.sax.ContentHandler):
#********** Begin *********#
infostart = False
def startElement(self,name,attrs):
self.infostart = name == "info"
def endElement(self,name):
self.infostart = False
def characters(self,content):
if self.infostart:
print(content)
#********** End **********#
def GetHandler():
return Handler()
第5关:XML篇:使用ElementTree解析XML
任务描述
本关任务:设计一个查询个人信息的小程序。
ElementTree
xml.etree.ElementTree模块是一个轻量级的 DOM(文件对象模型),具有方便友好的 API。代码可用性好,速度快,消耗内存少。
ElementTree模块大致可以三部分:ElementTree类,Element类以及一些操作 XML 的函数。
教程中所使用的data.xml文件的内容如下:
<collection shelf="New Arrivals">
<movie title="Enemy Behind">
<type>War, Thriller</type>
<description>Talk about a US-Japan war</description>
</movie>
<movie title="Transformers">
<type>Anime, Science Fiction</type>
<description>A schientific fiction</description>
</movie>
<movie title="Trigun">
<type>Anime, Action</type>
<description>Vash the Stampede!</description>
</movie>
<movie title="Ishtar">
<type>Comedy</type>
<description>Viewable boredom</description>
</movie>
</collection>
解析
xml.etree.ElementTree提供了两个函数:parse和fromstring,用于从文件和字符串解析 XML 数据,比如:
import xml.etree.ElementTree as ET
doc = ET.parse("data.xml") #从文件解析XML数据
root = doc.getroot() #获取根元素
data = "<a>hello</a>"
el = ET.fromstring(data) #从字符串解析XML数据
parse函数返回一个ElementTree对象,fromstring返回一个Element对象。
Element对象代表一个 XML 元素,它的功能接下来会介绍。
ElementTree对象代表一个 XML 文档,它提供了函数getroot,来获取一个文档的根元素,正如上面的例子中展示的那样。
查找元素
Element对象和ElementTree对象都提供了用于在子元素查找元素的函数:
find(name):用于在直接子元素中,查找一个名为name的元素;
findall(name):用于在直接子元素中,查找所有名为name的元素,它的返回值可以看做一个所有元素都是Element对象的tuple对象,可以对它进行迭代操作或者索引[]操作;
iter(name = None):用于在当前元素下的所有子元素中,查找名为name的元素,如果不指定name,则返回所有子元素。它返回一个迭代器对象。
比如:
import xml.etree.ElementTree as ET
doc = ET.parse("data.xml") #从文件解析XML数据
root = doc.getroot() #获取根元素
for s in root.findall("movie"): #选择所有名为moive的元素
print(s.find("description").text) #打印出movie元素下description元素的文本
得到结果是:
Talk about a US-Japan war
A schientific fiction
Vash the Stampede!
Viewable boredom
获取元素的文本与属性值
Element有一个属性text,这个属性用于获取直接在这个元素的开始、结束标志之间的文本。如果没有文本,则返回空字符串:
import xml.etree.ElementTree as ET
data = "<a>TextA<b>TextB</b></a>"
ele = ET.fromstring(data)
print(ele.text)
print(ele.find("b").text)
得到的结果是:
TextA
TextB
如果要获取一个元素的某个属性,可以使用get(name)函数,它会寻找当前元素上名为name的属性。如果找到就返回这个属性的值,没找到则返回空字符串。比如:
import xml.etree.ElementTree as ET
doc = ET.parse("data.xml") #从文件解析XML数据
root = doc.getroot() #获取根元素
for s in root.findall("movie"): #选择所有名为moive的元素
de = s.find("description") #获取description元素
print("%s: %s" % (s.get("title"),de.text)) #获取tile属性,与description元素的文本值格式化输出
得到的结果:
Enemy Behind: Talk about a US-Japan war
Transformers: A schientific fiction
Trigun: Vash the Stampede!
Ishtar: Viewable boredom
打印出了电影的名字与简介。
编程要求
根据提示,补充右侧编辑器Begin-End区间代码,完成InfoManager类的定义,实现如下的功能:
LoadInfo():从step5/data.xml中读取 XML 数据;
GetInfoCount():返回个人信息的条数,类型为int;
GetAge(name):返回名为name的人的年龄age,类型为int;
GetDescription(name):返回名为name的人的个人签名(元素的文本)。
为了简单起见,假设查询的人的信息一定存在。
测试说明
step5/data.xml中的数据如下:
<?xml version="1.0" encoding="utf-8"?>
<data count="4">
<info name="赵昊" age="15" >我赵昊第一个不服!</info>
<info name="龙傲天" age="16">我命由我不由天</info>
<info name="玛丽苏" age="15">我冷若冰霜,冷酷无情</info>
<info name="叶良辰" age="16">良辰日后必有重谢!</info>
</data>
评测代码会将查询到的信息,按照一定格式输出,请确保返回结果正确。
开始你的任务吧,祝你成功!
参考代码:
import xml.etree.ElementTree as ET
class InfoManager:
doc = None
root = None
def LoadInfo(self):
self.doc = ET.parse("step5/data.xml")
self.root = self.doc.getroot()
def GetInfoCount(self):
return int(self.root.get("count"))
def GetAge(self,name):
infos = self.root.findall("info")
for s in infos: #搜索name属性与函数参数name的值相同的xml元素
if s.get("name") == name:
return int(s.get("age"))
return 0 #随便返回一个值,评测代码不会有找不到的数据
def GetDescription(self,name):
infos = self.root.findall("info")
for s in infos: #搜索name属性与函数参数name的值相同的xml元素
if s.get("name") == name:
return s.text
return "" #随便返回一个值,评测代码不会有找不到的数据