클래스가 필요한 순간
- dict 에 dict을 저장해야 구현 가능하다고 느끼는 순간 클래스로 나누는것이 필요하다
- 아래 예시를 통해 어떻게 그런 순간을 맞닥뜨리는지 보자
class SimpleGradebook:
def __init__(self):
self._grades = {}
def add_student(self, name):
self._grades[name] = []
def report_grade(self, name, score):
self._grades[name].append(score)
def average_grade(self, name):
grades = self._grades[name]
return sum(grades) / len(grades)
- 위 클래스는 동적으로 구현되었음
- 학생들의 점수를 기록해야 하는데 학생들의 이름을 미리 알 수 없는 상황임
book = SimpleGradebook()
book.add_student('영희')
book.add_student('철수')
book.report_grade('영희', 70)
book.report_grade('영희', 75)
book.report_grade('철수', 83)
book.average_grade('영희'), book.average_grade('철수')
(72.5, 83.0)
- 이때 학생당 전체 성적이 아니라, 과목별 성적으로 나눠서 저장하고 싶다고 하자
- 위 코드로 설명한다면 key ‘영희’의 value에 새로운 dict 을 추가하고, 그 dict에 각 과목을 key로 지정해서 구현해야 한다
class BySubjectGradebook:
def __init__(self):
self._grades = {}
def add_student(self, name):
self._grades[name] = {}
def report_grade(self, name, subject, score):
grades = self._grades[name]
grades[subject] = score
def average_grade(self, name):
grades = self._grades[name]
scores = 0
for subject, score in grades.items():
scores += score
return scores / len(grades)
from collections import defaultdict
class BySubjectGradebook:
def __init__(self):
self._grades = {} # 외부 dict
def add_student(self, name):
self._grades[name] = defaultdict(list) # 내부 dict
def report_grade(self, name, subject, grade):
by_subject = self._grades[name]
grade_list = by_subject[subject] # defaultdict이 없었으면 이런식으로 append 못함
# 왜? grade_list = [] 이런식으로 매번 리스트 초기화시키면 append로 여러개 값을 추가 못 시키지
# 저렇게 key 주면 바로 지정한형식(여기서는 리스트)으로 바로 초기화 되게끔 해줌
grade_list.append(grade)
def average_grade(self, name):
by_subject = self._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('철수')
book.add_student('영희')
book.report_grade('영희', '수학', 70)
book.report_grade('영희', '수학', 764)
book.report_grade('영희', '영어', 85)
book.report_grade('철수', '체육', 151)
book.average_grade('영희'), book.average_grade('철수')
(306.3333333333333, 151.0)
- 이 상태에서 각 과목별로 중간고사, 기말고사 점수를 입력한 후 향후 가중치로 값 변동을 위해 점수와 가중치값을 같이 묶어서 저장하고 싶다고 함
class WeightedGradebook:
def __init__(self):
self._grades = {}
def add_student(self, name):
self._grades[name] = defaultdict(list)
def report_grade(self, name, subject, score, weight):
by_subject = self._grades[name]
grade_list = by_subject[subject]
grade_list.append((score, weight))
def average_grade(self, name):
by_subject = self._grades[name]
score_sum, score_count = 0, 0
for subject, scores in by_subject.items():
subject_avg, total_weight = 0, 0
for score, weight in scores:
subject_avg += score * weight
total_weight += weight
score_sum += subject_avg / total_weight
score_count += 1
return score_sum / score_count
book = WeightedGradebook()
book.add_student('알버트 아인슈타인')
book.report_grade('알버트 아인슈타인', '수학', 75, 0.05)
book.report_grade('알버트 아인슈타인', '수학', 65, 0.15)
book.report_grade('알버트 아인슈타인', '수학', 70, 0.80)
book.report_grade('알버트 아인슈타인', '체육', 100, 0.40)
book.report_grade('알버트 아인슈타인', '체육', 85, 0.60)
print(book.average_grade('알버트 아인슈타인'))
80.25
- for문도 읽기 어려워지고 사용법도 위치로 인자값 넣다보니까 정신 나감
- 클래스 분류가 필요한 시점!
# 책 보기 전 내가 먼저 구현해봄
from collections import defaultdict
# class Subject():
# def __init__(self, subject, score):
# subject, score
# Subject를 클래스로 만들지 못하겠음..
class Student():
grade = defaultdict(list)
# grade = {}
name = None
# def __init__(self, name):
# self.grade[name] = []
# self.name = name
def add_subject(self, subject, score, weight):
self.grade[subject].append((score, weight))
def get_grade(self):
print(self.grade)
# 평균 구하는건 귀찮아서 생략!
class Gradebook():
students = defaultdict(Student)
def add_student(self, name):
self.students[name] = Student()
gradebook = Gradebook()
gradebook.add_student('철수')
gradebook.students['철수'].add_subject('영어', 70, 0.3)
gradebook.students['철수'].add_subject('영어', 78, 0.7)
gradebook.students['철수'].get_grade()
# 철수 = Student()
# 철수.add_subject('수학', '70', 0.3)
# 철수.add_subject('수학', '85', 0.8)
# 철수.get_grade()
defaultdict(<class 'list'>, {'영어': [(70, 0.3), (78, 0.7)]})
from collections import namedtuple
Grade = namedtuple('Grade', ('score', 'weight'))
class Subject:
def __init__(self):
self._grades = []
def report_grade(self, score, weight):
self._grades.append(Grade(score, weight))
def average_grade(self):
total, total_weight = 0, 0
for grade in self._grades:
total += grade.score * grade.weight
total_weight += grade.weight
return total / total_weight
class Student:
def __init__(self):
self._subjects = defaultdict(Subject)
def get_subject(self, name):
return self._subjects[name]
def average_grade(self):
total, count = 0, 0
for subject in self._subjects.values():
total += subject.average_grade()
count += 1
return total / count
class Gradebook:
def __init__(self):
self._students = defaultdict(Student)
def get_student(self, name):
return self._students[name]
- Subject에 여러가지 점수(수행평가, 중간고사, 기말고사 등)가 들어올 수 있으니 list로 관리한다
- 그리고 여러 클래스들이 공유하게끔 하지 않고 인스턴스 하나만 사용할 수 있는 저장공간을 만들기 위해 self로 리스트를 초기화하는 모습을 볼 수 있다.
- Student 클래스는 여러 Subject 과목들을 관리하기 위해 Subject를 value로 받는 dictionary를 사용한다
- Gradebook 클래스는 여러 Student 학생들을 관리하기 위해 Student를 value로 받는 dictionary를 사용한다
위 피드백 코드를 잠깐 훑고 바로 다시 구현해보기
from collections import defaultdict
class Subject():
def __init__(self, name):
self.grade = defaultdict(list)
self.subj_name = name
self.scores = 0
self.total_weight = 0
def report_grade(self, score, weight):
self.grade[self.subj_name].append((score, weight))
def get_average_score(self):
scores = 0
# for dic_list in self.grade.values(): # value에 list를 넣었다고 해서 dictionary.values()가 list 타입으로 반환되는게 아니였음
for score, weight in list(self.grade.values())[0]:
scores += score
self.total_weight += weight
ave = scores / len(list(self.grade.values())[0])
self.scores = 0
self.total_weight = 0
return ave
class Student():
def __init__(self, stud_name):
self.grades = defaultdict(Subject)
self.stud_name = stud_name
def add_subject(self, subject:Subject):
self.grades[subject.subj_name] = subject
def get_average(self):
scores = 0
for subject_name, subject in self.grades.items():
scores += subject.get_average_score()
return scores / len(self.grades)
- 과목명, 가중치등이 어떻게 Subject class에 저장해야 할 지 고민했음
- 과목명은 포함되어야 할 것 같았지만, 가중치는 다른 과목과 연관되는 것이니 제외하려고 생각했었음
- 하지만 가중치가 결국 score와 1:1로 매칭되어야 하는 값이라고 최종 판단해서 저장하게 함
- 과목명은 포함되어야 할 것 같았지만, 가중치는 다른 과목과 연관되는 것이니 제외하려고 생각했었음
- score가 어떤 자료형으로 Subject class에 저장해야 할 지 고민했음
- 중간고사, 기말고사, 수행평가등 같은 과목이여도 여러 점수가 들어갈 수 있다는 생각에 list형태로 저장할 수 있는 defautdict(list) 사용
- dict.values를 통해서 list 타입이 반환되지 않는것을 깨달음.. list(self.grade.values())[0] 와 같이 코드가 해괴망측해짐
- 중간고사, 기말고사, 수행평가등 같은 과목이여도 여러 점수가 들어갈 수 있다는 생각에 list형태로 저장할 수 있는 defautdict(list) 사용
sub1 = Subject('과학')
sub1.report_grade(65, 0.3)
sub1.report_grade(72, 0.6)
sub2 = Subject('영어')
sub2.report_grade(45, 0.5)
sub2.report_grade(81, 0.7)
sub2.report_grade(77, 0.3)
print(f'{sub1.subj_name} 평균: {sub1.get_average_score()}, {sub2.subj_name} 평균: {sub2.get_average_score()}')
stu1 = Student('철수')
stu1.add_subject(sub1)
stu1.add_subject(sub2)
print(f'{stu1.stud_name} 평균: {stu1.get_average()}')
print(f'{stu1.stud_name} 평균: {stu1.get_average()}')
과학 평균: 68.5, 영어 평균: 67.66666666666667
철수 평균: 68.08333333333334
철수 평균: 68.08333333333334
최종적으로 다시 답안 코드 보기
from collections import namedtuple, defaultdict
Grade = namedtuple('Grade', ('score', 'weight'))
class Subject:
def __init__(self):
self._grades = []
def report_grade(self, score, weight):
self._grades.append(Grade(score, weight))
def average_grade(self):
total, total_weight = 0, 0
for grade in self._grades:
total += grade.score * grade.weight
total_weight += grade.weight
return total / total_weight
class Student:
def __init__(self):
self._subjects = defaultdict(Subject)
def get_subject(self, name):
return self._subjects[name]
def average_grade(self):
total, count = 0, 0
for subject in self._subjects.values():
total += subject.average_grade()
count += 1
return total / count
class Gradebook:
def __init__(self):
self._students = defaultdict(Student)
def get_student(self, name):
return self._students[name]
- 과목 이름을 Subject 클래스에서 따로 저장하지 않고 있는 모습을 볼 수 있음
- 과목 이름은 다른 과목들이 있을 때 구분 짓기 위한 것이기 때문에 과목 하나에 집중하는 Subject 클래스에서 따로 저장할 필요가 없음!
- Student 클래스에서는 이미 Subject 클래스에서 구현된 average_grade를 활용해 쉽게 average를 구하고 있는것을 확인 가능
book = Gradebook()
albert = book.get_student('알버트 아인슈타인')
math = albert.get_subject('수학')
math.report_grade(75, 0.05)
math.report_grade(65, 0.15)
math.report_grade(70, 0.80)
gym = albert.get_subject('체육')
gym.report_grade(100, 0.40)
gym.report_grade(85, 0.60)
print(albert.average_grade())
80.25
- get_subject 등으로 Student class에 Subject class를 저장하는게 어색해서 내 방식대로 구현해보자
class Subject():
def __init__ (self):
self.grades = []
def report_grade(self, score, weight):
self.grades.append((score, weight))
def get_average_score(self):
total_score = 0
total_weight = 0
for score, weight in self.grades:
total_score += score
total_weight += weight
return total_score / len(self.grades)
class Student():
def __init__(self):
self.subjects = defaultdict(Subject)
def add_subject(self, name, subject:Subject):
self.subjects[name] = subject
def get_average_score(self):
scores = 0
for name, subject in self.subjects.items(): # 이거 self.subjects.value() for문 적용해도 잘 되나? 될 것 같음.
print("출력")
print(subject.get_average_score())
scores += subject.get_average_score()
return scores / len(self.subjects)
sub1 = Subject()
sub2 = Subject()
sub3 = Subject()
sub1.report_grade(60, 0.3)
sub1.report_grade(75, 0.8)
sub1.report_grade(92, 0.7)
sub2.report_grade(60, 0.3)
sub2.report_grade(13, 0.3)
sub3.report_grade(45, 0.3)
sub3.report_grade(70, 0.3)
stu1 = Student()
stu1.add_subject('수학', sub1)
stu1.add_subject('과학', sub2)
stu1.add_subject('영어', sub3)
stu1.get_average_score()
출력
75.66666666666667
출력
36.5
출력
57.5
56.555555555555564
- 교재 방식과 내 방식에서 객체를 쓰는 방법이 조금 차이가 있다. 각 방식의 장점 혹은 단점이 존재할까? 계속 공부하면서 알아보자
PREVIOUS경제