# * dict와 tuple보다 class를 사용해야 할 때
# 손 쉬운 사용은 코드를 취약하게 한다
dict는 객체의 수명이 지속되는 동안 예측불가능한 식별자들을 관리하는 용도로 아주 좋다.
# 예시 1. 아이템을 드랍하는 몬스터
예를 들어, 다음 코드를 봐보자. 몬스터 별 드랍되는 아이템을 쉽게 지정하고 그 결과를 볼 수있다.
import random
class Monsters(object):
def __init__(self):
self._list = {}
def add_monster(self, name):
self._list[name] = []
def set_drop(self, name, item):
self._list[name].append(item)
def drop(self, name):
return random.choice(self._list[name])
monsters = Monsters()
monsters.add_monster('hidekuma')
monsters.set_drop('hidekuma', 'sword')
monsters.set_drop('hidekuma', 'shield')
print(monsters.drop('hidekuma'))
>>>
sword
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 예시 2. 아이템을 드랍하는 속성을 가진 몬스터
만약 몬스터가 추후에 더 많은 property(ex: HP, MP, 경험치 등)을 가진다고 가정하고, 드랍하는 아이템을 하나의 속성으로 간주하여, 다시 만들어보자.
import random
class Monsters2(object):
def __init__(self):
self._list = {}
def add_monster(self, name):
self._list[name] = {}
def set_drop(self, name, item):
monster = self._list[name]
drops = monster.setdefault('drops', [])
drops.append(item)
def drop(self, name):
return random.choice(self._list[name]['drops'])
monsters = Monsters2()
monsters.add_monster('hidekuma')
monsters.set_drop('hidekuma', 'spear')
monsters.set_drop('hidekuma', 'sword')
monsters.set_drop('hidekuma', 'shield')
print(monsters.drop('hidekuma'))
>>>
shield
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
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
아직까진 충분히 직관적이다.
# 예시 3. 속성을 가진 아이템을 드랍하는 속성을 가진 몬스터
가장 안쪽 딕셔너리에 아이템과 파워 그리고 랭크를 담은 tuple을 맵핑하였다.
import random
class Monsters3(object):
def __init__(self):
self._list = {}
def add_monster(self, name):
self._list[name] = {}
def set_drop(self, name, item, rank, power):
monster = self._list[name]
drops = monster.setdefault('drops', [])
drops.append((item, rank, power))
def drop(self, name):
return random.choice(self._list[name]['drops'])
monsters = Monsters3()
monsters.add_monster('hidekuma')
monsters.set_drop('hidekuma', 'C', 'spear', 10)
monsters.set_drop('hidekuma', 'B', 'sword', 12)
monsters.set_drop('hidekuma', 'A', 'shield', 17)
print(monsters.drop('hidekuma'))
>>>
('A', 'shield', 17)
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
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
이제 호출 시에도 처음에 비해 위치 인수들이 많아졌고, 점점 복잡해지기 시작하였다. 처음에는 단순히 몬스터의 드랍 아이템을 나타내기 위한 딕셔너리 였지만, 추후에 속성과 기능이 추가 될 수록 유지보수의 늪에 빠지게 될 것이다.
지금처럼 계층이 한 단계가 넘는 중첩은 피하는 것이 바람직하며, 캡슐화한 인터페이스를 제공하기 위해서 이런 상황에선 즉시 클래스로 옮겨가야한다.
# 두 단계 이상의 계층 구조는 클래스로 구현하자
먼저 아이템에는 공격력 외에도 공격 속도, 추가 버프들이 붙여질 가능성이 매우크다. 따라서, 튜플의 아이템이 두 개가 넘어갈 가능성이 매우 큰데, 이런 요구에 정확히 부합하는 것이 바로 namedtuple
이다.
# Immutable data class
namedtuple은 위치 인수나 키워드 인수로 생성가능하다.
import collections
Item = collections.namedtuple('Item', ('rank', 'name', 'power'))
1
2
3
2
3
collections.namedtuple
- namedtuple은 키워드 인수로 생성가능 하나, 기본값을 설정할 수 없다. 따라서 데이터의 선택적인 속성이 많아지면 다루기 힘들어진다.
- namedtuple은 여전히 숫자로 된 인덱스와 이터레이터 방법으로 순회가능하나, 상황에 따라선 의도와 다르게 사용될 수도 있기 때문에 사용에 항상 주의한다.
# 예시 4. 클래스 리팩토링
코드는 이전보다 길어졌지만, 호출부가 훨씬 이해하기 쉽고 예제도 명확하다.
import random
import collections
Item = collections.namedtuple('Item', ('rank', 'name', 'power'))
class Drops(object):
def __init__(self):
self._items = []
def set_drop(self, rank, item, power):
self._items.append(Item(rank, item, power))
def drop(self):
if self._items:
return random.choice(self._items)
else:
raise NotImplementedError
class Monster(object):
def __init__(self, name):
self._name = name
self._drops = Drops()
def __str__(self):
return self._name
def drops(self):
return self._drops
def drop(self):
return self._drops.drop()
class Monsters(object):
def __init__(self):
self._list = {}
def monster(self, name):
if name not in self._list:
self._list[name] = Monster(name)
return self._list[name]
monsters = Monsters()
hidekuma = monsters.monster('hidekuma')
drops = hidekuma.drops()
drops.set_drop('A', 'sword', 17)
print(hidekuma)
print(hidekuma.drop())
>>>
hidekuma
Item(rank='A', name='sword', power=17)
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 요약
- 중첩된 딕셔너리나 긴 튜플은 멀리하자
← * 키워드 전용인수 * 상태보존 클로저 →