파이썬은 다중 상속(편의와 캡슐화)을 처리할 수는 있지만 골치 아픈 경우를 피하기 위해선 mix-in을 사용할지 고려하라
믹스인은 자식 클래스가 사용할 메서드 몇 개만 정의하는 클래스
믹스인 클래스는 자체 애트리뷰트 정의가 없기에 믹스인 클래스의 init메서드를 호출할 필요도 없다
믹스인 장점
타입의 상관없이 객체의 현재 상태를 쉽게 들여다본다.
동적인 상태 접근이 가능하기에 제너릭하다
믹스인을 합성하거나 계층화를 해서 반복적인 코드를 최소화하고 재사용성을 최대화
"""제너릭하게 여러 클래스 활용
"""
class ToDictMixin:
def to_dict(self):
return self._traverse_dict(self.__dict__)
"""
_traverse_dict메서드는 hasattr을 통해서 동적인 애트리튜브 접근
isinstance를 사용한 타입 검사, __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
tree = BinaryTree(10,
left = BinaryTree(7, right=BinaryTree(9)),
right = BinaryTree(13, left=BinaryTree(11)))
print(tree.to_dict())
"""BinaryTreeWithParent._traverse메서드를 오버라이드하여 문제가 되는 값만 처리하여
무한루프를 돌지 못하게 한다.
"""
class BinaryTreeWithParent(BinaryTree):
def __init__(self, value, left=None,
right=None, parent=None):
super().__init__(value, left=left, right=right)
self.parent =parent
"""
부모를 가리키는 참조에 대해서 부모의 숫자값을 삽입
그렇지 않은 경우, super 내장함수를 통해서 디폴트 믹스인 구현 호출
"""
def _traverse(self, key, value):
if (isinstance(value, BinaryTreeWithParent) and
key == 'parent'):
return value.value #순환 참조 방지
else:
return super()._traverse(key, value)
#변환 시 순환 참조를 따라가지 않는다
root =BinaryTreeWithParent(10)
root.left = BinaryTreeWithParent(7, parent=root)
root.left.right = BinaryTreeWithParent(9, parent=root.left)
print(root.to_dict())
"""
BinaryTreeWithParent._traverse를 오버라이드하니
BinaryTreeWithParent를 애트리뷰트로 저장하는 모든 클래스도 자동으로 ToDictMixin
문제없이 사용
"""
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()) #무한 루프 없다
#믹스인 서로 합성
import json
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())
#믹스인이 있다면 JSON과 직렬화 혹은 역직렬화
#클래스 계층 구조를 쉽게, 번잡스러운 준비 코드 없이 만든다.
class DatacenterRack(ToDictMixin, JsonMixin):
def __init__(self, switch=None, machines =None):
self.switch = Switch(**switch)
self.machines = [
Machine(**kwargs) for kwargs in machines
]
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
#검사해보기
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)