[关闭]
@runzhliu 2018-02-09T03:53:35.000000Z 字数 2473 阅读 872

Python 辅助类代替字典和元组维持程序状态

Python 字典 元组


参考资料
《Effective Python》


通常人们习惯用 Python 的内置字典类型来保存对象在生命周期里的动态内部状态。举个简单的例子:

  1. class SimpleGradebook(object):
  2. def __init__(self):
  3. self._grades = {}
  4. def add_student(self, name):
  5. self._grades[name] = []
  6. def report_grades(self, name, score):
  7. self._grades[name].append(score)
  8. def average_grade(self, name):
  9. grades = self._grades[name]
  10. return sum(grades) / len(grades)

从示例程序来看,SimpleGradebook 这个类代表简易的成绩册的意义,通过用内部的 _grades 字典来保存学生的姓名和成绩,并且提供方法 report_grades 了来登记学生的姓名和成绩,此外还提供一个计算所有登记在册的学生的平均成绩的方法 average_grade

字典用起来是很方便的,并且效率也很高,计算复杂度很低。然后当需求增多的时候,其实并不好扩展,比如现在需要成绩册可以记录不同科目的成绩,并且可以根据不同科目来求平均成绩。

  1. class BySubjectGradebook(object):
  2. def __init__(self):
  3. self._grades = {}
  4. def add_student(self, name):
  5. self._grades[name] = {}
  6. def report_grade(self, name, subject, grade):
  7. by_subject = self._grades[name]
  8. grade_list = by_subject.setdefault(subject, [])
  9. grade_list.append(grade)
  10. def average_grade(self, name):
  11. by_subject = self._grades[name]
  12. total, count = 0, 0
  13. for grades in by_subject.values():
  14. total += sum(grades)
  15. count += len(grades)
  16. return total / count

上例中的类为 BySubjectGradebook,代表记录不同科目的成绩,可以看到,在录入成绩的方法 report_grade 增加科目参数,并且增加 grade_list 来记录不同科目的成绩列表,同时也可以看到 average_grade 计算成绩的平均分的方法代码膨胀,而已有嵌套循环,这让人头大......

然而这还不是最糟糕的,当年级长需要班主任提供一份该班的「平均成绩」,并且由于「素质教育」,各个科目都有相应权重,比如说语数英权重更高,音乐体育权重较低,所以成绩册还得加一个功能,就是计算加权平均分的功能。

  1. import collections
  2. Grade = collections.namedtuple('Grade', ('score', 'weight'))
  3. class Subject(object):
  4. def __init__(self):
  5. self._grades = []
  6. def report_grade(self, score, weight):
  7. self._grades.append(Grade(score, weight))
  8. def average_grade(self):
  9. total, total_weight = 0, 0
  10. for grade in self._grades:
  11. total += grade.score * grade.weight
  12. total_weight += grade.weight
  13. return total / total_weight
  14. class Student(object):
  15. def __init__(self):
  16. self._subjects = {}
  17. def subject(self, name):
  18. if name not in self._subjects:
  19. self._subjects[name] = Subject()
  20. return self._subjects[name]
  21. def average_grade(self):
  22. total, count = 0, 0
  23. for subject in self._subjects.values():
  24. total += subject.average_grade()
  25. count += 1
  26. return total / count
  27. class Gradebook(object):
  28. def __init__(self):
  29. self._students = {}
  30. def student(self, name):
  31. if name not in self._students:
  32. self._students[name] = Student()
  33. return self._students[name]
  34. if __name__ == '__main__':
  35. book = Gradebook()
  36. oscar = book.student('oscar')
  37. math = oscar.subject('Math')
  38. math.report_grade(80, 0.1)
  39. print oscar.average_grade()

可以看到新的具有计算加权平均分功能的类 Grade 代码量其实并不小,但是仔细去阅读的话,可以发现代码逻辑会更加清晰。利用 collections 模块中的 namedtuple 类型可以定义出精简而有不可变的数据数据类型。

上述代码中构建了一个 Grade 结构,会记录某科目的平均分和该科目的权重 weight,并且把科目、学生各构建一个辅助类来记录数据,这样使得最后的成绩册类 Gradebook 的表达更加清晰。在使用上,只需要新建一个 Student 的对象,然后再新建一个该学生的 subject 对象,再去登记分数和权重,最后调用计算加权平均分的方法即可。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注