Skip to content

Commit 812f565

Browse files
committed
Added item 22
1 parent 25c7220 commit 812f565

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed

item_22.py

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#!/usr/bin/env python3
2+
3+
'''Item 22 from Effective Python'''
4+
5+
6+
# Example 1
7+
''' You can define a class to store the names in a dictionary instead of using
8+
a predefined attribute for each student '''
9+
print('Example 1:\n==========')
10+
11+
12+
class SimpleGradebook(object):
13+
def __init__(self):
14+
self._grades = {}
15+
16+
def add_student(self, name):
17+
self._grades[name] = []
18+
19+
def report_grade(self, name, score):
20+
self._grades[name].append(score)
21+
22+
def average_grade(self, name):
23+
grades = self._grades[name]
24+
return sum(grades) / len(grades)
25+
26+
27+
# Example 2
28+
''' Using the class is simple '''
29+
print('\nExample 2:\n==========')
30+
31+
book = SimpleGradebook()
32+
book.add_student('Isaac Newton')
33+
book.report_grade('Isaac Newton', 90)
34+
book.report_grade('Isaac Newton', 95)
35+
book.report_grade('Isaac Newton', 85)
36+
print(book.average_grade('Isaac Newton'))
37+
38+
39+
# Example 3
40+
''' Dictionaries are so easy to use that there’s a danger of overextending them
41+
to write brittle code '''
42+
print('\nExample 3:\n==========')
43+
44+
class BySubjectGradebook(object):
45+
46+
def __init__(self):
47+
self._grades = {}
48+
49+
def add_student(self, name):
50+
self._grades[name] = {}
51+
52+
def report_grade(self, name, subject, grade):
53+
by_subject = self._grades[name]
54+
grade_list = by_subject.setdefault(subject, [])
55+
grade_list.append(grade)
56+
57+
def average_grade(self, name):
58+
by_subject = self._grades[name]
59+
total, count = 0, 0
60+
for grades in by_subject.values():
61+
total += sum(grades)
62+
count += len(grades)
63+
return total / count
64+
65+
66+
# Example 4
67+
''' Using the class remains simple '''
68+
print('\nExample 4:\n==========')
69+
70+
book = BySubjectGradebook()
71+
book.add_student('Albert Einstein')
72+
book.report_grade('Albert Einstein', 'Math', 75)
73+
book.report_grade('Albert Einstein', 'Math', 65)
74+
book.report_grade('Albert Einstein', 'Gym', 90)
75+
book.report_grade('Albert Einstein', 'Gym', 95)
76+
print(book.average_grade('Albert Einstein'))
77+
78+
79+
# Example 5
80+
''' instead of mapping subjects (the keys) to grades (the values), I can use
81+
the tuple (score, weight) as values '''
82+
print('\nExample 5:\n==========')
83+
84+
class WeightedGradebook(object):
85+
def __init__(self):
86+
self._grades = {}
87+
88+
def add_student(self, name):
89+
self._grades[name] = {}
90+
91+
def report_grade(self, name, subject, score, weight):
92+
by_subject = self._grades[name]
93+
grade_list = by_subject.setdefault(subject, [])
94+
grade_list.append((score, weight))
95+
96+
''' Although the changes to report_grade seem simple—just make the value a
97+
tuple—the average_grade method now has a loop within a loop and is
98+
difficult to read '''
99+
100+
def average_grade(self, name):
101+
by_subject = self._grades[name]
102+
score_sum, score_count = 0, 0
103+
for subject, scores in by_subject.items():
104+
subject_avg, total_weight = 0, 0
105+
for score, weight in scores:
106+
subject_avg += score * weight
107+
total_weight += weight
108+
score_sum += subject_avg / total_weight
109+
score_count += 1
110+
return score_sum / score_count
111+
112+
113+
# Example 6
114+
''' Using the class has also gotten more difficult. It's unclear what all of
115+
the numbers in the positional arguments mean. '''
116+
print('\nExample 6:\n==========')
117+
118+
book = WeightedGradebook()
119+
book.add_student('Albert Einstein')
120+
book.report_grade('Albert Einstein', 'Math', 80, 0.10)
121+
book.report_grade('Albert Einstein', 'Math', 80, 0.10)
122+
book.report_grade('Albert Einstein', 'Math', 70, 0.80)
123+
book.report_grade('Albert Einstein', 'Gym', 100, 0.40)
124+
book.report_grade('Albert Einstein', 'Gym', 85, 0.60)
125+
print(book.average_grade('Albert Einstein'))
126+
127+
128+
# Example 7
129+
''' Refactoring to classes '''
130+
print('\nExample 7:\n==========')
131+
132+
grades = []
133+
grades.append((95, 0.45))
134+
grades.append((85, 0.55))
135+
total = sum(score * weight for score, weight in grades)
136+
total_weight = sum(weight for _, weight in grades)
137+
average_grade = total / total_weight
138+
print(average_grade)
139+
140+
141+
# Example 8
142+
''' use _ to capture the third entry in the tuple and just ignore it '''
143+
print('\nExample 8:\n==========')
144+
145+
grades = []
146+
grades.append((95, 0.45, 'Great job'))
147+
grades.append((85, 0.55, 'Better next time'))
148+
total = sum(score * weight for score, weight, _ in grades)
149+
total_weight = sum(weight for _, weight, _ in grades)
150+
average_grade = total / total_weight
151+
print(average_grade)
152+
153+
154+
# Example 9
155+
''' The namedtuple type in the collections module lets you easily define tiny,
156+
immutable data classes '''
157+
print('\nExample 9:\n==========')
158+
159+
import collections
160+
Grade = collections.namedtuple('Grade', ('score', 'weight'))
161+
162+
163+
# Example 10
164+
''' write a class to represent a single subject that contains a set of grades
165+
'''
166+
print('\nExample 10:\n==========')
167+
168+
class Subject(object):
169+
def __init__(self):
170+
self._grades = []
171+
172+
def report_grade(self, score, weight):
173+
self._grades.append(Grade(score, weight))
174+
175+
def average_grade(self):
176+
total, total_weight = 0, 0
177+
for grade in self._grades:
178+
total += grade.score * grade.weight
179+
total_weight += grade.weight
180+
return total / total_weight
181+
182+
183+
# Example 11
184+
''' write a class to represent a set of subjects that are being studied by a
185+
single student '''
186+
print('\nExample 11:\n==========')
187+
188+
class Student(object):
189+
def __init__(self):
190+
self._subjects = {}
191+
192+
def subject(self, name):
193+
if name not in self._subjects:
194+
self._subjects[name] = Subject()
195+
return self._subjects[name]
196+
197+
def average_grade(self):
198+
total, count = 0, 0
199+
for subject in self._subjects.values():
200+
total += subject.average_grade()
201+
count += 1
202+
return total / count
203+
204+
205+
# Example 12
206+
''' write a container for all of the students keyed dynamically by their names
207+
'''
208+
print('\nExample 12:\n==========')
209+
210+
class Gradebook(object):
211+
def __init__(self):
212+
self._students = {}
213+
214+
def student(self, name):
215+
if name not in self._students:
216+
self._students[name] = Student()
217+
return self._students[name]
218+
219+
220+
# Example 13
221+
''' The line count of these classes is almost double the previous
222+
implementation's size. But this code is much easier to read. The example
223+
driving the classes is also more clear and extensible '''
224+
print('\nExample 13:\n==========')
225+
226+
book = Gradebook()
227+
albert = book.student('Albert Einstein')
228+
math = albert.subject('Math')
229+
math.report_grade(80, 0.10)
230+
math.report_grade(80, 0.10)
231+
math.report_grade(70, 0.80)
232+
gym = albert.subject('Gym')
233+
gym.report_grade(100, 0.40)
234+
gym.report_grade(85, 0.60)
235+
print(albert.average_grade())

0 commit comments

Comments
 (0)