Skip to content

Python 数据容器综合练习 ​

前面已经学过列表、元组、字典和集合。本节不引入新语法,集中解决三个问题:如何从嵌套数据中提取字段、如何统计文本中的单词、如何整理并比较课程报名数据。

遇到复杂数据时,先按下面的顺序拆解问题:

  1. 先判断最外层是什么容器
  2. 再确认目标数据位于哪一层
  3. 从外到内逐层取值
  4. 需要批量处理时,再加入循环

招聘信息嵌套结构的取值路径示意图

案例一:提取招聘信息 ​

问题:招聘数据由字典、列表和多层嵌套组成,如何从第一条招聘信息中提取 jobName、salaryDesc、jobLabels、jobExperience、jobDegree、cityName 和 areaDistrict?

案例数据
python
data = {
  "code": 0,
  "message": "Success",
  "zpData": {
    "resCount": 450,
    "filterString": "",
    "lid": "70vrcw3jkcv",
    "hasMore": True,
    "jobList": [
      {
        "securityId": "CJAYsQMRcIpBw-T1EB7SOsdlaZeK-20Hu72HUm9ps_MGbCOtv64w2N1oLSgOCnUcL5MRiwisuriEbNgrambqN1uIKU5cyShb6Lquqk_gfe3YXg~~",
        "bossAvatar": "https://img.bosszhipin.com/beijin/upload/avatar/20200301/16f40181c8c48be5b92145cf3707553a3a0b40d268d5e2075bc0a11cad38c31d_s.jpg",
        "bossCert": 3,
        "encryptBossId": "9938ab01894233453nJ60t6-FlE~",
        "bossName": "周先生",
        "bossTitle": "软件开发工程师",
        "goldHunter": 0,
        "bossOnline": False,
        "encryptJobId": "2cd54656375c9a101XZ_3NW6EFpR",
        "expectId": 0,
        "jobName": "Python 开发",
        "lid": "70vrcw3jkcv.search.1",
        "salaryDesc": "11-20K",
        "jobLabels": [
          "1-3 年",
          "本科"
        ],
        "jobValidStatus": 1,
        "iconWord": "",
        "skills": [
          "Go",
          "容器技术",
          "Linux",
          "NoSQL",
          "MySQL",
          "Redis",
          "MongoDB"
        ],
        "jobExperience": "1-3 年",
        "daysPerWeekDesc": "",
        "leastMonthDesc": "",
        "jobDegree": "本科",
        "cityName": "深圳",
        "areaDistrict": "龙岗区",
        "businessDistrict": "坂田",
        "jobType": 0,
        "proxyJob": 0,
        "proxyType": 0,
        "anonymous": 0,
        "outland": 0,
        "optimal": 0,
        "iconFlagList": None,
        "itemId": 1,
        "city": 101280600,
        "isShield": 0,
        "encryptBrandId": "02cd05cce753437e33V50w~~",
        "brandName": "华为",
        "brandLogo": "https://img.bosszhipin.com/beijin/upload/com/logo/20200429/8f64c9d0b30a53c434b26799ca78966cec9b49ecaf1604cb263748f03d5c2b0f.png",
        "brandStageName": "不需要融资",
        "brandIndustry": "计算机软件",
        "brandScaleName": "10000 人以上",
        "welfareList": [
          "零食下午茶",
          "年终奖",
          "员工旅游",
          "股票期权",
          "五险一金",
          "定期体检",
          "节日福利",
          "补充医疗保险",
          "加班补助",
          "餐补"
        ],
        "industry": 100021,
        "contact": False
      },
      {
        "securityId": "VbM2gY03-wESK-51LdXy4JGNTuenCanv_M0fYIdJ0du-jYUKSu5BHYfDAoGWGfPbjkaIuCdVyRvwBCB1vpOR9pCajiDxDQxByHcgBjy7YTbVbzomQw~~",
        "bossAvatar": "https://img.bosszhipin.com/beijin/mcs/useravatar/20180525/ef2fc44cc3c19bd7a04526f29c3c9e93e20c3ca1994cfe2bbaa4ac8e1111bc09_s.jpg",
        "bossCert": 3,
        "encryptBossId": "90479526e67044351H152NS4FlA~",
        "bossName": "刘先生",
        "bossTitle": "工程师",
        "goldHunter": 0,
        "bossOnline": False,
        "encryptJobId": "f9d845e08ea8228d1nx729i4F1BW",
        "expectId": 0,
        "jobName": "python MySQL 工程师",
        "lid": "70vrcw3jkcv.search.7",
        "salaryDesc": "3-7K",
        "jobLabels": [
          "经验不限",
          "本科"
        ],
        "jobValidStatus": 1,
        "iconWord": "",
        "skills": [
          "MySQL",
          "SQL",
          "网络爬虫技术",
          "Linux",
          "SQL Server",
          "Flask",
          "软件工程师",
          "Visual Studio"
        ],
        "jobExperience": "经验不限",
        "daysPerWeekDesc": "",
        "leastMonthDesc": "",
        "jobDegree": "本科",
        "cityName": "深圳",
        "areaDistrict": "龙岗区",
        "businessDistrict": "双龙",
        "jobType": 0,
        "proxyJob": 0,
        "proxyType": 0,
        "anonymous": 0,
        "outland": 0,
        "optimal": 0,
        "iconFlagList": None,
        "itemId": 7,
        "city": 101280600,
        "isShield": 0,
        "encryptBrandId": "c937ddf95e5282e21n1y3dq9FQ~~",
        "brandName": "鹏达软件",
        "brandLogo": "https://img.bosszhipin.com/beijin/mcs/chatphoto/20190301/b00cebeb9606bbacf5d4889056aad363e20c3ca1994cfe2bbaa4ac8e1111bc09.jpg",
        "brandStageName": "不需要融资",
        "brandIndustry": "计算机软件",
        "brandScaleName": "0-20 人",
        "welfareList": [
          "五险一金"
        ],
        "industry": 100021,
        "contact": False
      },
      {
        "securityId": "f5Luz9HTc1d3_-a16gUQQmlkMExHocgEtBcjR2x6S8Ue5xCfWZrVb7pdcoDBrUTXEt-NQETU4Z_qSv1nbGezJKQXLTMrEc2xTVEfrREbpgUZTpa4lg~~",
        "bossAvatar": "https://img.bosszhipin.com/boss/avatar/avatar_6.png",
        "bossCert": 3,
        "encryptBossId": "69b0c6ec51e75dc50nV93tS4GFFZ",
        "bossName": "李先生",
        "bossTitle": "hr",
        "goldHunter": 0,
        "bossOnline": False,
        "encryptJobId": "0d167c302eff61561XZy3t26FVFY",
        "expectId": 0,
        "jobName": "Python 开发工程师",
        "lid": "70vrcw3jkcv.search.8",
        "salaryDesc": "20-30K·15 薪",
        "jobLabels": [
          "3-5 年",
          "本科"
        ],
        "jobValidStatus": 1,
        "iconWord": "",
        "skills": [
          "数据结构",
          "架构师"
        ],
        "jobExperience": "3-5 年",
        "daysPerWeekDesc": "",
        "leastMonthDesc": "",
        "jobDegree": "本科",
        "cityName": "深圳",
        "areaDistrict": "南山区",
        "businessDistrict": "西丽",
        "jobType": 0,
        "proxyJob": 0,
        "proxyType": 0,
        "anonymous": 0,
        "outland": 0,
        "optimal": 0,
        "iconFlagList": None,
        "itemId": 8,
        "city": 101280600,
        "isShield": 0,
        "encryptBrandId": "49105119a08bf88c03dy09q6",
        "brandName": "Snapmaker",
        "brandLogo": "https://img.bosszhipin.com/beijin/mcs/banner/fcc4415f5c8e46b41a846960f117f30dcfcd208495d565ef66e7dff9f98764da.jpg",
        "brandStageName": "A 轮",
        "brandIndustry": "智能硬件",
        "brandScaleName": "100-499 人",
        "welfareList": [
          "零食下午茶",
          "团建聚餐",
          "五险一金",
          "法定节假日三薪",
          "节日福利",
          "季度团建",
          "带薪年假",
          "免费午餐",
          "股票期权",
          "免费工装",
          "每周体育活动",
          "生日会和礼物",
          "定期体检",
          "年终奖",
          "生日福利",
          "员工旅游"
        ],
        "industry": 100018,
        "contact": False
      },
    ],
    "totalCount": 300,
    "brandCard": None
  }
}

功能分析 ​

这份数据很长,不需要一次看懂所有字段。先定位目标数据的取值路径:

  1. data["zpData"] 取得招聘数据字典
  2. data["zpData"]["jobList"] 取得岗位列表
  3. data["zpData"]["jobList"][0] 取得第一条岗位字典
  4. 再通过字段名称取得岗位信息

先把第一条岗位字典保存到 job,后面的字段访问会更短,也更容易检查路径是否正确。

参考代码
python
job = data["zpData"]["jobList"][0]

print("岗位名称:", job["jobName"])
print("薪资范围:", job["salaryDesc"])
print("标签:", job["jobLabels"])
print("工作经验:", job["jobExperience"])
print("学历要求:", job["jobDegree"])
print("城市:", job["cityName"])
print("区域:", job["areaDistrict"])

预期输出:

text
岗位名称:Python 开发
薪资范围:11-20K
标签:['1-3 年', '本科']
工作经验:1-3 年
学历要求:本科
城市:深圳
区域:龙岗区

继续练习 ​

  1. 把第 1 条招聘信息打印出来
  2. 把第 2 条招聘信息的相同字段也打印出来
  3. 使用 for 循环,把 jobList 中每一条岗位的岗位名和薪资都打印出来
  4. 再额外提取 brandName 和 skills

案例二:统计单词 ​

问题:如何配合字符串、列表和字典,统计下面这段《Python 之禅》中每个单词出现的次数?

注意:这次统计的是单词,不是字符。

python
# python 之禅
s = """
The Zen of Python, by Tim Peters
Beautiful is better than ugly
Explicit is better than implicit
Simple is better than complex
Complex is better than complicated
Flat is better than nested
Sparse is better than dense
Readability counts
Special cases aren't special enough to break the rules
Although practicality beats purity
Errors should never pass silently
Unless explicitly silenced
In the face of ambiguity, refuse the temptation to guess
There should be one-- and preferably only one --obvious way to do it
Although that way may not be obvious at first unless you're Dutch
Now is better than never
Although never is often better than *right* now
If the implementation is hard to explain, it's a bad idea
If the implementation is easy to explain, it may be a good idea
Namespaces are one honking great idea -- let's do more of those
"""

功能分析 ​

先把问题拆成四步:

  1. 先把大字符串拆成单词列表
  2. 创建空字典保存统计结果
  3. 遍历单词列表,新增或更新对应次数
  4. 遍历字典并输出统计结果

单词统计从字符串到字典的处理流程图

参考代码
python
words = s.split()

word_count = {}
for word in words:
    if word not in word_count:
        word_count[word] = 1
    else:
        word_count[word] += 1

for word, count in word_count.items():
    print(f"{word}: {count}")

运行后可以看到,is、better 等重复单词会记录对应次数,字典的键保存单词,值保存次数。

标点和大小写问题 ​

基础版本可以运行,但还要识别两个数据清洗问题:

像 explain、explain,、Python, 和 *right* 这类内容,标点符号也会被保留。The 和 the 也会被当成两个不同的单词。

如果要让统计更准确,可以继续做两件事:

  1. 统一大小写
  2. 去掉标点符号

本节先完成基础版,重点是掌握“遍历文本并更新字典”的过程。

案例三:整理课程报名数据 ​

问题:固定的学员报名记录如何用元组保存,课程名称如何转换成集合完成去重和比较?

已知两名学员的报名数据:

python
student_a = ("小明", ("Python", "MySQL", "Python", "Linux"))
student_b = ("小红", ("Python", "Redis", "Redis", "Linux"))

需要完成四项任务:

  1. 打印两名学员的姓名
  2. 分别去除每名学员重复报名的课程
  3. 找出两名学员共同报名的课程
  4. 找出只被其中一名学员报名的课程

功能分析:

  1. 元组索引 0 保存姓名,索引 1 保存课程元组
  2. 使用 set() 把课程元组转换成集合并去重
  3. 使用交集找共同课程
  4. 使用对称差集找只在一边出现的课程

课程报名数据去重与集合比较示意图

参考代码
python
student_a = ("小明", ("Python", "MySQL", "Python", "Linux"))
student_b = ("小红", ("Python", "Redis", "Redis", "Linux"))

course_set_a = set(student_a[1])
course_set_b = set(student_b[1])

print("学员:", student_a[0])
print("去重后的课程:", course_set_a)

print("学员:", student_b[0])
print("去重后的课程:", course_set_b)

print("共同课程:", course_set_a & course_set_b)
print("仅一人报名的课程:", course_set_a ^ course_set_b)

集合的显示顺序可能不同,只要结果中的课程名称一致即可。

小结 ​

本节通过三个问题,集中训练四项能力:

  1. 如何识别列表、元组和字典组成的嵌套结构
  2. 如何从外到内拆解取值路径
  3. 如何使用字典完成“数据 → 次数”的统计
  4. 如何使用元组保存固定记录,并使用集合完成去重和比较

练习 ​

  1. 提取第三条招聘信息的岗位名、薪资、公司名称和技能要求
  2. 使用 for 循环输出所有岗位的名称、薪资和工作地点
  3. 在单词统计案例中,把所有单词转成小写后再统计
  4. 尝试去掉单词两端的常见标点符号
  5. 在课程报名案例中,找出小明报名、小红没有报名的课程