- [BW 37] 내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라2024년 11월 11일
- 31514
- 작성자
- 2024.11.11.:20
클래스와 상속을 사용하는 방법을 잘 알아두면 유지 보수하기 쉬운 코드를 작성할 수 있다.
파이썬 내장 딕셔너리 타입을 사용하면 객체의 생명 주기 동안 동적인 내부 상태를 잘 유지할 수 있다.
동적이라는 말은 어떤 값이 들어올지 미리 알 수 없는 식별자들을 유지해야 한다는 뜻이다.
예를 들어 학생들의 점수를 기록해야 하는데, 이름은 미리 알 수 없는 상황이라고 하자.
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.report_grade('홍길동', 90) book.report_grade('홍길동', 95) book.report_grade('홍길동', 85) print(book.average_grade('홍길동')) >>> 90.0
이 클래스는 쉽게 사용할 수 있지만, 딕셔너리와 관련된 내장 타입은 사용하기 쉬우므로 과하게 확장하면서 깨지기 쉬운 코드를 작성할 위험성이 있다.
예를 들어 SimpleGradeBook 클래스를 확장해서 전체 성적이 아니라 과목별 성적을 리스트로 저장한다고 하자.
from collections import defaultdict class SimpleGradeBook: def __init__(self): self._grades = {} def add_student(self, name): self._grades[name] = defaultdict(list) def report_grade(self, name, subject, score): by_subject = self._grades[name] grade_list = by_subject[subject] grade_list.append(score) def average_grade(self, name): by_subject = self._grades[name] total, count = 0, 0 for scores in by_subject.values(): total += sum(scores) count += len(scores) return total / count book = SimpleGradeBook() book.add_student('홍길동') book.report_grade('홍길동', '수학', 90) book.report_grade('홍길동', '수학', 10) book.report_grade('홍길동', '영어', 95) book.report_grade('홍길동', '영어', 70) book.report_grade('홍길동', '국어', 85) book.report_grade('홍길동', '국어', 40) print(book.average_grade('홍길동')) >>> 65.0
아직까지는 충분히 복잡도를 관리할 수 있을 거 같다.
하지만 각 점수의 가중치를 함께 저장한다면 어떨까?
def report_grade(self, name, subject, score, weight): by_subject = self._grades[name] grade_list = by_subject[subject] grade_list.append((score, weight))
이때부터 클래스도 쓰기 어려워지고, 어떤 값이 어떤 뜻을 가지는지 이해하기 어렵다.
이럴 때 기능을 클래스로 분리하면 비록 코드는 길어지지만, 더 읽기 쉽고 유지보수하기 쉬운 코드가 완성된다.
from collections import defaultdict, 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] book = GradeBook() hong = book.get_student('홍길동') math = hong.get_subject('수학') math.report_grade(75, 0.05) math.report_grade(65, 0.15) math.report_grade(70, 0.80) gym = hong.get_subject('체육') gym.report_grade(100, 0.40) gym.report_grade(85, 0.60) print(hong.average_grade()) >>> 80.25
namedtuple을 사용하면 작은 불변 데이터 클래스를 쉽게 정의할 수 있다.
'Book > 파이썬 코딩의 기술' 카테고리의 다른 글
[BW 33] yield from을 사용해 여러 제너레이터를 합성하라 (1) 2024.11.08 [BW 32] 긴 리스트 컴프리헨션보다는 제네레이터 식을 사용하라 (2) 2024.11.01 [BW 31] 인자에 대해 이터레이션할 때는 방어적이 돼라 (1) 2024.10.31 [BW 30] 리스트를 반환하기보다는 제너레이터를 사용하라 (2) 2024.10.30 [BW 29] 대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라 (0) 2024.10.29 다음글이전글이전 글이 없습니다.댓글
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)