P2 工程化基础_哔哩哔哩_bilibili
00:05 ,工程方法重写,usr/bin脚本头
02:45 ,if name == ‘main
06:40 ,各种下划线含义,file
14:30 ,封装函数,select_annotations,函数注释
16:55 ,变量类型申明概述
19:30 ,doctest
23:45 ,字符串,r字符串
30:20 ,f字符串
33:06 ,不要过度注释

33:40 ,splitcocodata
35:55 ,assert断言
37:44 ,抽象出数据拆分功能
43:37 ,下标用浮点数的bug
46:00 ,个人习惯对__main
套with TicToc,支持出错情况下的计时
47:35 ,补充:tests,ujson,jupyter运行顺序

54:40 ,random伪随机数原理,种子作用
58:20 ,in-place原地操作,list.sort与sorted(list)区别
64:10 ,args,*kwargs,打包解包

前面我们探索,分析了解决问题的基本思路、算法。
现在我们尝试用更工程化、规范的代码求解该问题。

1 基本框架

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # @Author : 陈坤泽
  4. # @Email : 877362867@qq.com
  5. # @Date : 2021/07/14 10:00

搜狗输入法自定义短语设置_代号4101的专栏-CSDN博客


if name == ‘main‘:
Python中的下划线(译文) - SegmentFault 思否


筛选函数的封装
函数的注释
:param注释
Specify types with docstrings | PyCharm


代码的运行顺序;dprint
jupyter运行顺序

2 字符串

字符串的几种写法
r字符串(常用于windows路径)与f字符串(扩展学习资料
字符串的拼接

3 doctest

不同run技巧


扩展:detectron2的tests,不太重要,有兴趣的自己放大图片看~
image.png

4 定义函数/传参/打包解包

函数的参数 - 廖雪峰的官方网站
args,*kwargs
解包操作(return多值也是解包操作)


**kwargs,可以方便传参:
image.png

5 随机数

seed种子的含义,基本原理 y=(a*x+b)%c,与作用
shuffle随机打乱
in-place原地操作(sort、sorted区别;pandas操作)

6 完整代码/xlpr2021_splitdata.py

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # @Author : 陈坤泽
  4. # @Email : 877362867@qq.com
  5. # @Date : 2021/03/28
  6. """ 本代码是XLPR 2021级研究生编程考核题的参考答案
  7. 【教学】每个脚本的第一个三引号字符串,可以对脚本进行一个整体的介绍
  8. """
  9. import json
  10. import random
  11. def select_annotations(annotations, image_ids):
  12. """ 筛选出对应图片的annotations
  13. 【关于函数注释】
  14. 1、py的函数标准注释方法,要写在内部开头三引号字符串里
  15. 2、并建议第一行写功能摘要
  16. 3、:param这种字眼是pycharm自动生成的标准注释结构
  17. 有些特别简单的函数,也没必要非把每个参数、return都解释一遍
  18. 注释本身也有维护成本,有时候代码改了,但注释没改,会给其他使用者带来更大的迷惑性和误导
  19. 【关于封装】
  20. 因为这段“很简单”,本函数可以选择不封装,但封装后代码可读性会略强些。
  21. 但写代码还是要注意不要“过度封装”,何谓过度封装一两句话讲不清,
  22. 要自己多写代码、多学习别人、开源的优秀代码去感悟。
  23. 本函数也可以选择直接嵌套在 split_coco_data 里,对外隐藏,py支持在函数里定义函数
  24. 【doctest】
  25. 这里是doctest文档测试,不仅是使用示例,也是一个测试点。
  26. 这个功能只有按规范在三引号字符串里写才有。
  27. >>> annotations = [{'image_id': 1, 'box_id': 1}, {'image_id': 1, 'box_id': 2}, {'image_id': 2, 'box_id': 3}]
  28. >>> select_annotations(annotations, {1, 3})
  29. [{'image_id': 1, 'box_id': 1}, {'image_id': 1, 'box_id': 2}]
  30. """
  31. # 简单的for循环和if操作,可以用“列表推导式”写
  32. return [an for an in annotations if (an['image_id'] in image_ids)]
  33. def split_coco_data(file, parts, *, shuffle=True):
  34. """ 数据拆分器,这个数据是coco格式,所以称为coco_data
  35. :param file: 数据文件,用json格式存储着coco格式的字典
  36. 含有images、annotations、categories三个字段
  37. 本参数也可以设计为传入data字典 (这段看不懂没关系,可以假装不存在)
  38. 但由于random.shuffle是in-place原地操作,
  39. 为了避免修改原始数据,或者要多一步繁琐的copy操作,把读文件也封装了。
  40. 这样这个函数就是完全独立的一个功能组件,不会对外部产生任何难以预知的影响。
  41. :param dict parts: 每个部分要拆分、写入的文件名,以及数据比例
  42. py≥3.6的版本中,dict的key是有序的,会按顺序处理开发者输入的清单
  43. 这里比例求和可以不满1,但不能超过1
  44. :param bool shuffle: 是否打乱原有images顺序
  45. """
  46. # 1 读入data (使用编号注释对代码逻辑分块,能大大提高可读性)
  47. with open(file, 'r') as f:
  48. data = json.load(f)
  49. if shuffle: # 是否打乱应该设计为一个可选参数,可能会存在某些特殊场合需要维持原数据顺序
  50. random.shuffle(data['images'])
  51. assert sum(parts.values()) <= 1, '比例和不能超过1' # 为了工程鲁棒,常用assert进行简单的规范检查
  52. # 2 生成每一个部分的文件
  53. total_num, used_rate = len(data['images']), 0 # 注意py可以这样在一行赋值多个变量 (这个本质是“解包”操作)
  54. for k, v in parts.items():
  55. # 2.1 选择子集图片 (代码块出现嵌套逻辑,可以用2.1、2.2的子编号格式来表达)
  56. images = data['images'][int(used_rate * total_num):int((used_rate + v) * total_num)]
  57. # 注意这里存成了哈希索引的set集合类型,提高检索效率,算法整体复杂度只有O(n)
  58. # 如果set是树结构二分查找,复杂度是O(n*log n)
  59. # 有人用双循环检索的话,复杂度是O(n^2),速度就很慢
  60. # 怎么方便、定量、严谨分析算法性能,可以参考前面讲的pyxllib库的TicToc、PerfTest工具
  61. image_ids = {im['id'] for im in images} # 改成花括号,列表推导式也能直接生成set类型
  62. # 2.2 生成新的字典
  63. part_data = {'images': images,
  64. 'annotations': select_annotations(data['annotations'], image_ids),
  65. 'categories': data['categories']}
  66. # 2.3 写入文件
  67. # f字符串:f'{k}.json' 等价于 str(k) + '.json'
  68. with open(f'{k}.json', 'w', encoding='utf8') as f:
  69. json.dump(part_data, f)
  70. # 2.4 更新使用率
  71. used_rate += v
  72. if __name__ == '__main__':
  73. # 不要把执行代码放在外面,养成好习惯放在if __name__ == '__main__'里面,并对有一定规模的代码都封装成函数
  74. split_coco_data('publaynet.json', {'train': 0.6, 'val': 0.2, 'test': 0.2})

7 留个题目给大家

先尝试在IDE中对这个函数执行doctest:

  1. def add(a, b):
  2. """
  3. >>> add(3, 4)
  4. 7
  5. >>> add(-3.5, 5)
  6. 1.5
  7. >>> add('hello,', 'world!') # 鸭子类型,输入字符串也可以拼接
  8. 'hello,world!'
  9. >>> add('hello\t', 'world!')
  10. 'hello\tworld!'
  11. """
  12. return a + b

image.png


然后分析为什么这样写测试行不通,应该如何修改?

  1. import re
  2. def add(a, b):
  3. """
  4. >>> add(r'hello\t', 'world!')
  5. 'hello\\tworld!'
  6. 在add函数不一定非要测add函数的功能,其实是可以检查任意表达式的。
  7. 例如,正则的测试,也会有类似问题:
  8. >>> re.sub(r'\\+', '', r'ab\\c\d\t') # 删除字符串中的所有斜杠
  9. 'abcdt'
  10. """
  11. return a + b

image.png

甚至打开Python Console会发现”没问题”啊,为什么doctest过不了?
image.png