파이썬에서 딕셔너리를 다루는 더 나은 방법에 대해 이야기해보려고 합니다.
1) dict(key=value) 사용하기
우리가 흔히 알고 있는 딕셔너리 생성 방법은 중괄호 {}를 사용하는 것입니다. 하지만 dict(key=value)로 생성하는 방법은 더 깔끔하고 직관적입니다.
# 일반적으로 dictionary를 생성할때
d = {'apple':4,'orange':5,'pear':6,'pineapple':7}
# 더 나은 방법으로 똑같이 dictionary를 생성하는법
d = dict(apple=4, orange=5, pear=6, pineapple=7)
dict(key=value) 장점:
- {}를 사용할 때, 문자열 키에 따옴표를 입력할 필요가 없습니다.
- 예를 들어, 'apple', 'orange' 등과 같이 말이죠.
- 더 많은 키를 다뤄야 할수록 따옴표를 입력해야 하는 것이 귀찮아집니다.
- dict()를 사용할 때는 따옴표를 무시할 수 있습니다.
물론, dict() 방식은 문자열이 아닌 키에는 작동하지 않기 때문에, 두 방법 모두 각자의 용도가 있습니다.
2) ** 사용해서 딕셔너리 합치기
# 여기에 2 dicts가 있습니다.
a = {1:1, 2:2}
b = {3:3, 4:4}
# ** 를 사용함으로써 쉽게 a, b dicts를 합칠 수 있습니다.
x = {**a, **b}
print(x) # {1:1, 2:2, 3:3, 4:4}
- 딕셔너리 앞의 **는 키-값 쌍을 부모 딕셔너리로 언패킹합니다.
# 일반적인 key-value도 같이 추가할 수 있습니다.
a = {1:1, 2:2}
b = {3:3, 4:4}
x = {**a, **b, 5:5}
print(x) # {1:1, 2:2, 3:3, 4:4, 5:5}
3) **를 사용하여 딕셔너리를 키워드 인자로 전달하기
def test(a, b, c):
print(a, b, c)
test(a=1, c=2, b=3) # 1 3 2
우리는 a, b, c를 키로 가지는 딕셔너리를 이 함수에 동적으로 전달할 수 있습니다.
mydict = dict(a=1, b=2, c=3)
print(mydict) # {'a':1, 'b':2, 'c':3}
# test(a=1, b=2, c=3) 로 사용하는것과 동일합니다.
test(**mydict) # 1 2 3
^ 딕셔너리 앞의 **는 다시 한 번 그 키-값 쌍들을 test 함수로 언패킹합니다.
참고: 이는 함수에 키워드 인자를 동적으로 전달하고 싶을 때 유용합니다.
4) 딕셔너리 컴프리헨션
{1:1, 2:4, 3:9, 4:16}와 같은 딕셔너리를 만들고 싶다고 가정해 봅시다.
# 일반적으로 dict를 만들때
d = {}
for iin range(1,5):
d[i] = i**2
print(d) # {1: 1, 2: 4, 3: 9, 4: 16}
# dict comprehension를 써서 만들때
d = {i:i**2 for i in range(1, 5)}
print(d) # {1:1, 2:4, 3:9, 4:16}
두 방법 모두 맞는 방법입니다. 하지만 딕셔너리 컴프리헨션이 훨씬 더 우아하고, 파이썬스러우며, 읽기 쉽다는 점을 주목하세요.
# 2중 for문
d = {}
for i in range(2):
for j in range(2,4):
d[(i, j)] =0
print(d) # {(0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0}
# dict comprehension에서 2중 for문 사용법
d = {(i, j):0 for i in range(2)for j in range(2, 4)}
print(d) # {(0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0}
5) dict.get(key, default_value)
존재하지 않는 키에 접근할 때, 우리는 KeyError를 얻습니다.
d = {1:1, 2:2, 3:3}
print(d[1]) # 1
print(d[4]) # KeyError
.get() 메서드를 사용하여 KeyError를 피할 수 있습니다. 이 메서드는 키가 존재하지 않을 경우 None을 반환합니다.
# .get() 사용할때
d = {1:1, 2:2, 3:3}
print(d.get(1)) # 1
print(d.get(4)) # None
KeyError를 발생시키는 대신 None을 얻는다는 점을 주목하세요.
# .get() 커스텀 기본값
d = {1:1, 2:2, 3:3}
print(d.get(1, 100)) # 1
print(d.get(4, 100)) # 100
print(d.get(9, 100)) # 100
커스텀 기본값도 정의할 수 있습니다.
6) 튜플 리스트를 사용하여 dict() 생성하기
ls = [('apple', 4), ('orange', 5), ('pear', 6)]
# 이것을 dict()에 전달하여 딕셔너리를 생성할 수 있습니다
d = dict(ls)
print(d) # {'apple': 4, 'orange': 5, 'pear': 6}
딕셔너리 컴프리헨션을 작성하지 않고도 튜플에서 빠르게 딕셔너리를 생성할 수 있습니다.
7) .items() and .values()
d = dict(apple=4, orange=5, pear=6)
print(d) # {'apple':4, 'orange':5, 'pear':6}
딕셔너리 자체를 순회할 때, 우리는 단순히 모든 딕셔너리 키를 생성합니다:
for k in d:
print(k)
# apple
# orange
# pear
**.values()**를 사용하면 모든 딕셔너리 값을 생성합니다:
for v in d.values():
print(v)
# 4
# 5
# 6
**.items()**를 사용하면, 키와 값을 튜플로 모두 생성합니다:
for k, v in d.items():
print(k, v)
# apple 4
# orange 5
# pear 6
개인적으로 **.items()**가 딕셔너리의 모든 키-값 쌍을 빠르게 순회하는 데 가장 유용한 메서드라고 생각합니다.
8) 딕셔너리 키가 될 수 있는 것과 될 수 없는 것
참고:
- 불변 데이터 타입은 딕셔너리 키가 될 수 있습니다. 예: int str tuple bool
- 가변 데이터 타입은 될 수 없습니다. 예: list dict
# 불변 데이터 타입을 딕셔너리 키로 사용하려는 시도
mylist = [1,2,3]
d = {mylist:5}
# TypeError: unhashable type: 'list'
어떤 객체가 딕셔너리 키로 사용될 수 있는지 확인하려면 내장 hash() 함수를 사용할 수 있습니다.
# hash() 메서드를 불변 타입에 사용할때
a:int = 4
b:str = 'hi'
print(hash(a)) # 4
print(hash(b)) # -4763374371541057969
# hash() 메서드를 가변 타입에 사용할때
l:list = [1,2,3]
d:dict = {1:1}
print(hash(l)) # TypeError: unhashable type: 'list'
print(hash(d)) # TypeError: unhashable type: 'dict'
따라서 딕셔너리 키가 될 수 있는 커스텀 객체를 만들고 싶다면, __hash__ 메서드를 사용하여 커스텀 객체를 어떻게 해시할지 정의할 수 있습니다.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
return hash(str(self.name) + str(self.age))
dog1 = Dog('rocky', 4)
dog2 = Dog('fifi', 5)
d = {dog1:1, dog2:2}
print(d)
# {<__main__.Dog object at 0x10476a9f0>: 1, <__main__.Dog object at 0x10476aa20>: 2}