Home 파이썬 코딩의 기술(5) 클래스와 인터페이스
Post
Cancel

파이썬 코딩의 기술(5) 클래스와 인터페이스

  • 책 파이썬 코딩의 기술을 읽고 정리한 내용입니다. Book_Logo

    5.클래스와 인터페이스

    37. 내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라

    내장 타입(리스트, 딕셔너리, 튜플 등)을 여러 단계 합성시키면, 코드가 복잡해진다.
    이를 피하기 위해 코드를 여러 클래스로 나누어 복잡도를 낮추고, 확장성을 높일 수 있다.
    특히 간단한 자료구조의 경우 namedtuple을 활용하면 좋다.
    ```py from collections import namedtuple Grade = namedtuple(‘Grade’, (‘score’, ‘weight’))

class Subject: def init(self): self._grades = []

1
2
3
4
5
6
7
8
9
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 ``` 그러나 namedtuple의 경우, 디폴트 인자값을 지정할 수 없다는 단점을 지닌다.

38. 간단한 인터페이스의 경우 클래스 대신 함수를 받아라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_missing():
    print('키 추가됨')
    return 0

from collections import defaultdict
current = {'초록': 12, '파랑': 3}
increments = [
    ('빨강', 5),
    ('파랑', 17),
    ('주황', 9),
]
result = defaultdict(log_missing, current)
for key, amount in increments:
    result[key] += amount

위와 같은 방식으로 함수를 인자로 전달하여, 원하는 부수효과를 얻을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
class BetterCountMissing:
    def __init__(self):
        self.added = 0

    def __call__(self):
        self.added += 1
        return 0

counter = BetterCountMissing()
result = defaultdict(counter, current) # __call__에 의존함
for key, amount in increments:
    result[key] += amount

또한 보다 복잡한 효과를 원할 경우, 클래스와 call 메소드를 활용할 수 있다.


39. 객체를 제너릭하게 구성하려면 @classmethod를 통한 다형성을 활용하라


40. super로 부모 클래스를 초기화하라

자식 클래스에서 부모 클래스의 이름을 활용하여 초기화하는 경우 생기는 문제점은,

  1. 다중 상속일 경우 부모 클래스의 초기화 순서가 아닌, 클래스의 정의에서 부모 클래스를 나열한 순서에 영향을 받는다는 점에서 오는 혼란
  2. 다이아몬드 상속에서 같은 조상의 초기화가 여러번 이루어질 수 있다는 점

반면, super를 사용할 경우, mro 순서에 의해 혼란이 사라진다.

41. 기능을 합성할 때는 믹스인 클래스를 사용하라

믹스인 클래스: 자식 클래스가 사용할 메서드 몇 개만 정의하는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ToDictMixin:
    def to_dict(self):
        return self._traverse_dict(self.__dict__)

    def _traverse_dict(self, instance_dict):
        output = {}
        for key, value in instance_dict.items():
            output[key] = self._traverse(key, value)
        return output

    def _traverse(self, key, value):
        if isinstance(value, ToDictMixin):
            return value.to_dict()
        elif isinstance(value, dict):
            return self._traverse_dict(value)
        elif isinstance(value, list):
            return [self._traverse(key, i) for i in value]
        elif hasattr(value, '__dict__'):
            return self._traverse_dict(value.__dict__)
        else:
            return value

class BinaryTree(ToDictMixin):
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

특히 변경점이 있을 경우 쉽게 오버라이드 할 수 있다는 장점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
class BinaryTreeWithParent(BinaryTree):
    def __init__(self, value, left=None,
                 right=None, parent=None):
        super().__init__(value, left=left, right=right)
        self.parent = parent

    def _traverse(self, key, value):
        if (isinstance(value, BinaryTreeWithParent) and
                key == 'parent'):
            return value.value  # Prevent cycles
        else:
            return super()._traverse(key, value)

42. 비공개 애트리뷰트보다는 공개 애트리뷰트를 사용하라

파이썬의 애트리뷰트에는 공개 애트리뷰트와 비공개 애트리뷰트가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyObject:
    def __init__(self):
        self.public_field = 5
        self.__private_field = 10

    def get_private_field(self):
        return self.__private_field

foo = MyObject()
print(foo.public_field)
print(foo.__private_field) # 에러 발생
# 올바른 접근법
print(foo.get_private_field)
print(foo._MyObject__private_field)

비공개 애트리뷰트를 사용하는 것의 단점은,

  1. 자식 클래스에서 애트리뷰트의 접근이 바로 되지 않으며
  2. 자식 클래스에서 접근을 위해, self._MyObject__private_field 와 같은 식을 사용해야하는데, 이는 클래스의 확장성을 낮춘다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
lass MyBaseClass:
    def __init__(self, value):
        self.__value = value

    def get_value(self):
        return self.__value

class MyStringClass(MyBaseClass):
    def get_value(self):
        return str(super().get_value())  # 변경됨

class MyIntegerSubclass(MyStringClass):
    def get_value(self):
        return int(self._MyStringClass__value)  # 변경되지 않음

반면 비공개 애트리뷰트를 사용해야하는 유일한 경우는 하위클래스와의 애트리뷰트의 정의가 충돌이 예상될 때이다.


43. 커스텀 컨테이너 타입은 collections.abc를 상속하라

기존 파이썬 자료구조를 상속하지 않고 새로운 컨테이너 타입을 만들고 싶다면, collections.abc를 상속해야한다. 이를 통해 새로운 컨테이너를 만들때 구현하는것을 빠트린 메소드를 쉽게 안정적으로 찾을 수 있다.

This post is licensed under CC BY 4.0 by the author.

파이썬 코딩의 기술(4) 컴프리헨션과 제너레이터

정수론(1) 최대공약수의 새로운 정의