[Effective Python] 5. Classes and Interfaces

Stop._.bmin·2023년 1월 28일
0

Effective-python

목록 보기
5/5

Item 37: Compose Classes Instead of Nesting Many Levels of Built-in Types

파이썬에 내장되어 있는 딕셔너리 타입

  • 객체의 수명이 지속되는 동안 동적인 내부 상태를 관리하는 용도로 wonderful하다.
    • dynamic: 예상하지 못한 식별자를 관리해야 하는 상황

교재 예제

  • 학생들의 이름을 저장하고 싶을 때 (이름을 모르는 학생 집단의 성적을 관리)
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]
        if not len(grades):
            raise ValueError("입력된 성적이 없습니다")
        return sum(grades) / len(grades)
  • using the class is simple (나는 아직 객체지향에 대한 이해가 부족..)
book = SimpleGradebook()
book.add_student('Issac Newton')
book.report_grade('Issac Newton', 90)
book.report_grade('Issac Newton', 95)
book.report_grade('Issac Newton', 85)

>>> print(book.average_grade('Issac Newton'))

90.0
  • 반 별로 성적표 클래스를 만들어서 내부 딕셔너리에 둔 다음에 학생의 이름과 성적을 각각 keyvalue 로 집어넣은 것이다.

그러나 필자에 따르면, 딕셔너리는 쓰기 쉬워서 overextending하다가 코드를 취약하게 만들 수 있다. 가령, SimpleGradbook 클래스를 확장하여 사용하지 않고 과목별로 저장한 다음에 dict 으로 매핑할 수 있다. 즉 딕셔너리 안에 딕셔너리가 들어가는 형태

class BySubjectGradebook:
    def __init__(self): 						# outer dict
        self._grades = {}

    def add_student(self, name):
        self._grades[name] = defaultdict		# inner dict

    def report_grade(self, name, subject, grade):
        by_subject = self._grades[name]
        grade_list = by_subject.setdefault(subject, [])
        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

위에까지는 나름 다룰만 하다고 필자는 주장한다

클래스를 활용하는 것도 simple하다

book = BySubjectGradebook()
book.add_student('Albert Einstein')
book.report_grade('Albert Einstein', 'Math', 75)
book.report_grade('Albert Einstein', 'Math', 65)
book.report_grade('Albert Einstein', 'Gym', 90)
book.report_grade('Albert Einstein', 'Gym', 95)
print(book.average_grade('Albert Einstein'))
>>> 81.25

파이썬의 내장된 dict나 tuple을 활용하면 레이어를 추가하여 계속 진행하기 쉽다. 그러나 둘 이상이 될 때는 하지 않는 것이 좋다. (다른 사람이 보기 좋지 않고, 유지와 관리가 어렵다)

  • 따라서 이를 깨닫는다면, 클래스로 나누어 데이터를 잘 캡슐화함으로서 잘 정의된 interface를 제공할 수 있다. (필자에 따르면 인터페이스와 구체적인 구현 간에 추상화 계층을 만들 수 있다고 한다.)

Refactoring to Classes

  • 리펙토링을 하는 것에는 다양한 접근 방식이 있다.
  • 만약, 의존관계에 있다면 가장 하위의 개념인 성적부터 클래스로 옮겨보는 것이다.
  • 간단한 정보를 담기에 A class는 다소 무거우므로 튜플로 사용한다.
grades = []
grades.append((95, 0.45))
grades.append((85, 0.55))
total = sum(score * weight for score, weight in grades)
total_weight = sum(weight for _, weight in grades)
average_grade = total / total_weight

파이썬에서는 딱히 필요없는 변수일 경우 _를 활용한다.

그런데 위의 코드의 문제는 일반 튜플은 위치에 의존한다.
따라서 아래와 같은 코드를 제시하였다

grades = []
grades.append((95, 0.45, 'Great job'))
grades.append((85, 0.55, 'Better next time'))
total = sum(score * weight for score, weight, _ in grades)
total_weight = sum(weight for _, weight, _ in grades)
average_grade = total / total_weight

이런 패턴의 튜플은 deepening layers of dictionaries와 유사하다. (딕셔너리의 계층을 깊게 두는 방식)
만약 튜플의 아이템이 2개를 넘어가면 다른 방법을 고려하는디 이때 collections 모듈의 namedtuple 을 활용하면 해결할 수 있다.

from collections import namedtuple

Grade = namedtuple('Grade', ('score', 'weight'))

이 fields는 named attribute 로 접근할 수 있다. (문자열 이름을 속성으로 접근할 수 있다.)

  • 그리고 필자가 namedtuple 에 관한 한계를 일부 정리해두었다.

Item 38: Accept Functions Instead of Classes for Simple Interfaces

  • 인터페이스가 간단하면 클래스 대신 함수를 받자

Python에서 함수는 first-class function이다. 즉 1급 객체이다

파이썬의 내장 api의 상당수는 함수를 넘겨서 동작을 사용자화하는 기능이 있다. hook를 이용해서 작성한 코드를 실행 중에 호출한다고 한다.
가령, list의 sort 메소드에는 key라는 인자를 받을 수 있다.

names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle']
names.sort(key=len)
print(names)

collections 모듈의 defaultdict

이 파트는 이해가 잘 안되어서 ㅠㅠ 공란으로 잠시 두고 스터디 이후에 제대로 채우도록 하겠습니다

정리

  • 클래스를 정의하고 인스턴스화하는 대신 Python의 구성 요소 간 간단한 인터페이스에 함수를 사용할 수 있다.
  • Python의 함수 및 메서드에 대한 참조는 일급입니다. 즉, 다른 유형과 마찬가지로 표현식에 사용할 수 있다.
  • call 특수 메서드를 사용하면 클래스의 인스턴스를 일반 Python 함수처럼 호출할 수 있다.
  • 상태 유지를 위한 함수가 필요한 경우 상태 저장 클로저를 정의하는 대신 call 메서드를 제공하는 클래스를 정의하는 것을 고려하자.


Item 43: Inherit from collections.abc for Custom Container Types

  • 커스텀 컨테이너 타입은 collections.abc의 클래스를 상속받게 만들자

sequence처럼 간단한 클래스를 설계할 때는 list타입에서 상속받으려고 하는게 당연하다.

필자는 아래와 같은 예제코드를 제시해 주었다.

class FrequencyList(list):
    def __init__(self, members):
        super().__init__(members)
    def frequency(self):
        counts = {}
        for item in self:
            counts[item] = counts.get(item, 0) + 1
        return counts
profile
원하는 만큼만

0개의 댓글