클래스와 인터페이스 (2)

About_work·2023년 2월 11일
0

python 기초

목록 보기
14/65

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

요약

  • super().__init__(~~~)처럼 아무인자 없이 호출하면, 최상위 부모부터 차례대로 초기화해준다. (초기화 순서와, 다이아몬드 상속 문제를 알아서 해결해준다.)
  • super(BaseAgents(클래스이름), base_agents(클래스객체)).__init__(~~~) 처럼 호출하면, 해당 인자의 클래스의 부모들만 초기화해준다.

41: 기능을 합성할 때는 mix-in 클래스를 사용하라.

요약

  • mix-in 클래스를 사용해 구현할 수 있는 기능을, “instance attribute와 __init__을 사용하는 다중 상속”을 통해 구현하지 말라.
  • mix-in 클래스가 클래스별로 특화된 기능을 필요로 한다면, instance 수준에서 끼워 넣을 수 있는 기능(정해진 메서드를 통해 해당 기능을 instance가 제공하게 만듦)을 활용하라.
  • mix-in 에는 필요에 따라 instance method는 물론 class method도 포함될 수 있다.
  • mix-in을 합성하면, 단순한 동작으로부터 더 복잡한 기능을 만들어낼 수 있다.

본문

  • mix-in 클래스
    • 자식 class가 사용할 몇개의 method만 정의하는 클래스
    • 자체 attribute가 없다. 그러므로 __init__메서드를 호출할 필요도 없다.
    • 다중 상속으로 인해 발생할 수 있는 문제를 피하기 위해 사용하자!
    • 장점: generic 기능을 쉽게 연결할 수 있고, 필요할 때 기존 기능을 다른 기능으로 override 해 변경할 수 있다는 것이다.
  • 아래 코드는, 메모리 내에 들어있는 파이썬 객체를 직렬화에 사용할 수 있도록 하는 기능을 generic하게 작성하여 여러 class에 활용하는 예시이다.
tree = BinaryTree(10,
                  left=BinaryTree(7, right=BinaryTree(9)),
                  right=BinaryTree(13, left=BinaryTree(11)))
print(tree.to_dict())

>>>
{‘value’: 10,
‘left’: {‘value’: 7, ‘left’: None, ‘right’: {‘value’: 9, ‘left’: None, ‘right’: None}},
‘right’: {‘value’: 13, ‘left’: {‘value’: 9, ‘left’: None, ‘right’: None}, ‘right’:None}
}

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

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
  • 코드 2
    • mix-in의 가장 큰 장점은, generic 기능을 쉽게 연결할 수 있고, 필요할 떄 기존 기능을 다른 기능으로 override해 변경할 수 있다는 것이다.
  • 아래 코드처럼, mix-in의 _traverse 메서드를 하위 클래스가 override하여, 문제가 되는 값만 처리하면 된다.

root = BinaryTreeWithParent(10)
root.left = BinaryTreeWithParent(7, parent=root)
root.left.right = BinaryTreeWithParent(9, parent=root.left)
print(root.to_dict())

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)
>>>
{‘value’: 10,
‘left’: {‘value’: 7, ‘left’: none, ‘right’: {‘value’: 9, ‘left’: None, ‘right’: None, ‘parent’: 7}, ‘parent’: 10},
‘right’: None, ‘parent’: None}
}
  • 코드 3

class NamedSubTree(ToDictMixin):
    def __init__(self, name, tree_with_parent):
        self.name = name
        self.tree_with_parent = tree_with_parent

my_tree = NamedSubTree('foobar', root.left.right)
print(my_tree.to_dict()) # 무한 루프없음

>>>
{‘name’: ‘foobar’, ‘tree_with_parent’: {‘value’: 9, ‘left’: None, ‘right’: None, ‘parent’: 7}}
  • 코드 4 (mix-in을 서로 합성)
    • 임의의 class를 json으로 직렬화하는 generic mix-in을 만들자.

serialized = """{
    "switch": {"ports": 5, "speed": 1e9},
    "machines": [
        {"cores": 8, "ram": 32e9, "disk": 5e12},
        {"cores": 4, "ram": 16e9, "disk": 1e12},
        {"cores": 2, "ram": 4e9, "disk": 500e9}
    ]
}"""

deserialized = DatacenterRack.from_json(serialized)
roundtrip = deserialized.to_json()
assert json.loads(serialized) == json.loads(roundtrip)

class DatacenterRack(ToDictMixin, JsonMixin):
    def __init__(self, switch=None, machines=None):
        self.switch = Switch(**switch)
        self.machines = [
            Machine(**kwargs) for kwargs in machines]

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

class JsonMixin:
    @classmethod
    def from_json(cls, data):
        kwargs = json.loads(data)
        return cls(**kwargs)

    def to_json(self):
        return json.dumps(self.to_dict())

class Switch(ToDictMixin, JsonMixin):
    def __init__(self, ports=None, speed=None):
        self.ports = ports
        self.speed = speed

class Machine(ToDictMixin, JsonMixin):
    def __init__(self, cores=None, ram=None, disk=None):
        self.cores = cores
        self.ram = ram
        self.disk = disk

42: 비공개 attribute 보다는, 공개 attribute를 사용하라.

요약

  • 파이썬 컴파일러는, private attribute를 자식 class나 class 외부에서 사용하지 못하도록 엄격히 금지하지 못한다.
  • 여러분의 내부 API에 있는 class의 하위 class를 정의하는 사람들이, 여러분이 제공하는 class의 attribute를 사용하지 못하도록 막기보다는, attribute를 사용해 더 많은 일을 할 수 있게 허용하라.
  • private attribute로 (외부나 하위 클래스의) 접근을 막으려고 시도하기보다는, protect field를 사용하면서 문서에 적절한 가이드를 남겨라.
  • 여러분이 코드 작성을 제어할 수 없는 하위 클래스에서, 이름 충돌이 일어나는 경우를 막고 싶을 때에만 private attribute를 사용할 것을 권한다.

기본 정보

  • _ABC
    • 보호 (protect) 필드
    • 보호되야 하는 instance attribute
    • 클래스, 함수 등의 이름으로도 쓰일 수 있다.
      • from module import * 을 하면, _로 시작하는 모든 객체들은 무시된다.
  • __ABC
    • 비공개(private) 필드
    • 한 클래스 안에서만 쓰이고 다른 곳에서는 쓰면 안되는 경우 (안에서만 호출이 되는 용도)
    • 외부에서 instance.__ABC 를 호출하면 에러가 난다.
    • 변수 뿐만 아니라, method 이름에도 적용이 가능하다.
    • 더블 언더스코어가 붙은 class의 attribute 이름은 맹글링되어 class 간 속성명끼리의 충돌을 방지한다.
    • 특정 class가 부모 class의 변수를 override 하는 것을 방지한다.

43: custom container 타입은 collections.abc를 상속하라.

요약

  • 간편하게 사용할 경우에는 python container type(list나 dictionary 등)을 직접 상속하라.
  • custom container을 제대로 구현하려면, 수많은 method를 구현해야 한다는 점에 주의하라.
  • custom container 타입이, collection.abc에 정의된 interface를 상속하면, custom container type이 정상적으로 작동하기 위해 필요한 interface와 기능을 제대로 구현하도록 보장할 수 있다.

기본 정보

  • 컨테이너 ??
    • 간단 설명: 컨테이너는 sequence(list와 tuple 등)+ mapping(dictionary 등)
    • __contain__ 메서드가 구현되어있는 객체
    • 자료형에 상관없이 저장이 가능한 객체
    • 여러 데이터 객체에 대한 메모리 참조 정보를 담고 있는 객체
    • 문자열(str), 튜플(tuple), 리스트(list), 딕셔터리(dictionary), 집합(set) 등은 타입에 무관하게 저장이 가능한 컨테이너 객체
    • 정수, 실수, 복소수 등은 타입이 고정되어 있는 단일 종류(Literal)한 자료형

본문

  • 모든 python class는 함수와 attribute를 함께 캡슐화하는 일종의 container라고 할 수있다.
  • sequence 처럼 사용법이 간단한 class를 정의할 떄는, 파이썬 내장 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

foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd'])
print('길이: ', len(foo))

foo.pop()
print('pop한 다음:', repr(foo))
print('빈도:', foo.frequency())
  • custom container을 제대로 구현하려면, collections.abc를 이용하라.
    • 꼭 정의해줘야 하는 메서드 구현을 알아서 알려준다.

#
from collections.abc import Sequence

class BadType(Sequence):
    pass

foo = BadType()

>>>
Traceback …
TypeError: Can`t instantiate abstract class BadType with abstract methods __getitem__, __len__
  • Set이나 MutableMapping과 같이, 파이썬의 관례에 맞춰 구현해야 하는 magic method가 훨씬 더 많은 더 복잡한 container type을 구현할 때는, 이런 추상 기반 클래스가 주는 이점이 더 커진다.
profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글