python组合和继承_⾼效Python代码——类与继承
⼀、⽤辅助类(⽽不是字典)来维护程序的状态
Python 内置的字典类型可以很好地保存某个对象在其⽣命周期中的(动态)内部状态。
如下⾯的成绩单类:
class SimpleGradebook(object):
def __init__(lf):
lf._grades = {}
def add_student(lf, name):
lf._grades[name] = []
def report_grade(lf, name, score):
lf._grades[name].append(score)
def average_grade(lf, name):
grades = lf._grades[name]
return sum(grades) / len(grades)
book = SimpleGradebook()
book.add_student('Isaac Newton')
print(book.average_grade('Isaac Newton'))
# => 85.0
在上⾯的 SimpleGradebook 类中,学⽣名字及其对应的成绩都保存在 _grades 字典结构中,这样就不必把每个学⽣都表⽰成对象并预设⼀个⽤于存放名字的属性了。
字典类型⽤起来⽅便,但也容易因为过度使⽤导致⼀些问题。如果需要扩充上⾯成绩单类的功能,把学⽣成绩按照科⽬保存。则 _grades 字典中需要嵌⼊另⼀个字典存储科⽬与多次成绩的键值对。
即类似这样的结构:{'Einstein': {'Math': [80, 90]}}
class BySubjectGradebook(object):
def __init__(lf):
lf._grades = {}
def add_student(lf, name):
lf._grades[name] = {}
def report_grade(lf, name, subject, grade):
by_subject = lf._grades[name]
grade_list = by_subject.tdefault(subject, [])
grade_list.append(grade)
def average_grade(lf, name):
by_subject = lf._grades[name]
total, count = 0, 0
for grades in by_subject.values():
total += sum(grades)
count += len(grades)
return total / count
book = BySubjectGradebook()
book.add_student('Albert Einstein')
contraction
print(book.average_grade('Albert Einstein'))
# => 80.0
假设需求再次改变,在记录某个分数的同时,还需要记录该次成绩占该科⽬历次成绩的权重。。。此时⽤于保存成绩的数据结构可以改成这样:
{'Einstein': {'Math': [(80, 0.4), (90, 0.6)]}}
但是对于新的 average_grade ⽅法来说,处理上述数据记录的代码就⽐较难以理解了。
把嵌套结构重构为类
⽤来保存程序状态的数据结构⼀旦过于复杂(如包含多层嵌套),则应该将其拆解为类,提供更为明确的接⼝,同时更好的封装数据。
from collections import namedtuple
Grade = namedtuple('Grade', ('score', 'weight'))
# 科⽬类。_grades 属性⽤于保存带权重的分数(Grade())对象
# average_grade ⽅法⽤于按权重计算本科成绩的平均分
class Subject(object):埃尔维斯 普雷斯利
def __init__(lf):
lf._grades = []
def report_grade(lf, score, weight):
lf._grades.append(Grade(score, weight))
def average_grade(lf):
total, total_weight = 0, 0
for grade in lf._grades:
total += grade.score * grade.weight
耳子total_weight += grade.weight
return total / total_weight
# 学⽣类。_subjects 属性⽤于保存该学⽣的所有科⽬(Subject())对象
# average_grade ⽅法⽤于计算该学⽣所有科⽬成绩的平均分
class Student(object):
def __init__(lf):
lf._subjects = {}
def subject(lf, name):
if name not in lf._subjects:
lf._subjects[name] = Subject()
纯正英语发音return lf._subjects[name]
def average_grade(lf):
英语诗歌total, count = 0, 0
for subject in lf._subjects.values():
total += subject.average_grade()
count += 1
return total / count
# 成绩单类。_students 属性保存所有的学⽣(Student())对象
class Gradebook(object):
def __init__(lf):
lf._students = {}
def student(lf, name):
if name not in lf._students:
lf._students[name] = Student()
return lf._students[name]
book = Gradebook()
albert = book.student('Albert Einstein')
math = albert.subject('Math')
dotcom
print(albert.average_grade())
# => 86.0
要点
尽量不使⽤多层嵌套的字典(如包含其他字典的字典)存储程序状态,也不要使⽤过长的元组
容器中包含简单⼜不可变的数据,可以先使⽤ namedtuple 表⽰,有需要时再改为完整的类
⽤于保存程序内部状态的字典若过于复杂,应将其拆解为多个辅助类
⼆、⽤ super 初始化⽗类
初始化⽗类的传统⽅式,是在⼦类⾥直接调⽤⽗类的 __init__ ⽅法:
class MyBaClass(object):
def __init__(lf, value):
lf.value = value
vivian什么意思class MyChildClass(MyBaClass):
def __init__(lf):
MyBaClass.__init__(lf, 5)
上述⽅法对于简单的继承⾏为是可⾏的,但是很多情况下仍会出现问题。
⾸先若⼦类受到多重继承的影响,则直接调⽤⽗类的 __init__ ⽅法会产⽣⽆法预知的⾏为(调⽤顺序不确定)。class MyBaClass(object):
def __init__(lf, value):
lf.value = value
class TimesTwo(object):
def __init__(lf):
lf.value *= 2
class PlusFive(object):
def __init__(lf):
lf.value += 5
class OneWay(MyBaClass, TimesTwo, PlusFive):
def __init__(lf, value):
MyBaClass.__init__(lf, value)
TimesTwo.__init__(lf)
PlusFive.__init__(lf)
class AnotherWay(MyBaClass, TimesTwo, PlusFive):
def __init__(lf, value):
MyBaClass.__init__(lf, value)
PlusFive.__init__(lf)
TimesTwo.__init__(lf)
foo = OneWay(5)
print(f"First ordering is (5 * 2) + 5 = {foo.value}")
bar = AnotherWay(5)
print(f"Second ordering still is {foo.value}")
OneWay 与 AnotherWay 中定义了两种完全不同的调⽤⽗类 __init__ ⽅法的顺序,但实际执⾏的结果却是相同的,导致⼦类代码中定义的调⽤顺序与⼦类实际产⽣的⾏为不⼀致。
此外在菱形继承中,直接调⽤⽗类的构造器也会出现问题。菱形继承是指⼦类继承⾃两个单独的超类,⽽这两个⽗类⼜都继承⾃同⼀个公共基类。这种继承⽅式会使菱形顶部的公共基类多次执⾏其 __init__ ⽅法,产⽣意想不到的⾏为。
修女也疯狂主题曲
class MyBaClass(object):
def __init__(lf, value):
lf.value = value
class TimesFive(MyBaClass):
lsp是什么意思
def __init__(lf, value):
MyBaClass.__init__(lf, value)
lf.value *= 5
class PlusTwo(MyBaClass):
def __init__(lf, value):
MyBaClass.__init__(lf, value)
lf.value += 2
class ThisWay(TimesFive, PlusTwo):
短裤英文def __init__(lf, value):
TimesFive.__init__(lf, value)
PlusTwo.__init__(lf, value)
foo = ThisWay(5)
print(f"Should be (5 * 5) + 2 = 27 but actully is {foo.value}")
# => Should be (5 * 5) + 2 = 27 but actully is 7
最终结果为 7,原因是在调⽤第⼆个⽗类的构造器(即 PlusTwo.__init__)时,公共基类的构造器(即 MyBaClass.__init__)会再次被调⽤导致 value 重新变成 5,⽽不能保持 TimesFive.__init__ 之后的 25。
通过 super 调⽤⽗类的构造器:
class MyBaClass(object):
def __init__(lf, value):
lf.value = value
class TimesFive(MyBaClass):
def __init__(lf, value):
super(__class__, lf).__init__(value)
lf.value *= 5
class PlusTwo(MyBaClass):
def __init__(lf, value):