개발서적

[파이썬 코딩의 기술] 2. 리스트와 딕셔너리(2-1)

비아 VIA 2022. 7. 24. 23:39

읽은 범위
2장 리스트와 딕셔너리

목차

  • 시퀀스를 슬라이싱하는 방법을 익혀라
  • 스트라이드와 슬라이스를 한 식에 함께 사용하지 말라
  • 슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용하라
  • 복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라
  • 딕셔너리 삽입 순서에 의존할 때는 조심하라
  • in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라
  • 내부상태에서 원소가 없는 경우를 처리할 때는 setdefault보다 defaultdict를 사용하라
  • '__missing__'을 사용해 키에 따라 다른 디폴트 값을 생성하는 방법을 알아두라

11. 시퀀스를 슬라이싱하는 방법을 익혀라

슬라이싱은 범위를 넘어가는 시작 인덱스나 끝 인덱스도 허용한다. 따라서 시퀀스의 시작이나 끝에서 길이를 제한하는 슬라이스를 쉽게 표현할 수 있다.

리스트의 슬라이싱은 종종 사용하긴 했는데 책에서는 그동안 써본적이 없던 사용법을 몇가지 알게 되었다.

예를 들면 보통 아래처럼 리스트에 지정한 슬라이스 길이보다 대입되는 배열의 길이가 더 짧으면 리스트가 줄어들고 반대로 리스트에 지정한 슬라이스 길이보다 대입되는 배열의 길이가 더 길면 리스트가 늘어난다.

a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

print('이전', a)
a[2:7] = [11, 22, 33]

>>>
이전: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
이후: ['a', 'b', 11, 22, 33, 'h']

 

print('이전', a)
a[2:3] = [44, 55]
print('이후', a)

>>>
이전: ['a', 'b', 11, 22, 33,'h']
이후: ['a', 'b', 44, 55, 22, 33, 'h']

일부러 헷갈리게 이런 코드를 만드는 경우는 많지 않을 수도 있지만 만약 실수로 리스트가 기대하는 값보다 늘어나거나 줄어들었을 때 이런 케이스를 염두해해볼 수 있다. 또한 슬라이싱에서 시작과 끝 인덱스를 모두 생략하면 원래 리스트를 복사한 새로운 리스트를 얻는 것도 알게 되었다. 하지만 시작과 끝 인덱스가 없는 슬라이스에 대입하면 슬라이스가 참조하는 리스트의 내용을 대입하는 리스트의 복사본을 얻는다.

# case1
b = a[:]
assert b == a and b is a

# case2
b = a
print('이전 a:', a)
print('이전 b:', b)
a[:] = [100, 200, 300]
assert a is b
print('이후 a:', a)
print('이후 b:', b)

>>>
이전 a: ['a', 'b', 44, 55, 22, 33, 'h']
이전 b: ['a', 'b', 44, 55, 22, 33, 'h']
이후 a: [100, 200, 300]
이후 b: [100, 200, 300]

12. 스트라이드와 슬라이스를 한 식에 함께 사용하지 말라

한 슬라이스 안에서 시작, 끝 증가값을 함께 사용하지 말라. 세 파라미터를 모두 써야 하는 경우, 두 번 대입을 사용(한 번은 스트라이딩, 한 번은 슬라이싱)하거나 itertools 내장 모듈의 isslice를 사용하라

사실 스트라이드는 일할때 많이 사용하는 편이 아니다. 코딩테스트를 공부할 때만 종종 사용해본 것 같다. 꼭 필요한 경우라면 사용하겠지만 책에서처럼 스트라이드와 슬라이스를 한 식에 쓰는 일은 더더욱 없을 것 같다. 코드를 쉽게 읽을 수도 없고 내가 예측한 것과 다른 방식으로 작동할 수도 있기 때문이다. 

파이썬은 리스트[시작:끝:증가값]으로 증가값으로 지정한 일정한 간격을 두고 슬라이싱을 할 수 있는 특별한 구문을 제공한다. 이를 스트라이드라고 한다.
numbers = [1, 2, 3, 4, 5, 6]
odds = numbers[::2]
evens = numbers[1::2]
print(odds)
print(evens)

>>>
[1, 3, 5]
[2, 4, 6]

 

13. 슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용하라

리스트를 서로 겹치지 않게 여러 조각으로 나눌 경우, 슬라이싱과 인덱싱을 사용하기보다는 나머지를 모두 잡아내는 언패킹을 사용해야 실수할 여지가 훨씬 줄어든다

리스트에 여러개의 요소가 있는 상황에서 인덱스나 슬라이스로 하위 집합으로 나누게 되면 하위 집합을 사용할 때 인덱스 사용 실수로 오류가 나기 쉽다. 그래서 별표식을 사용하여 나머지의 모든 값을 담는 언패킹을 할 수 있다. 보통은 여러 변수에 언패킹을 할 때 별표식을 가장 나중에 오는 변수에 사용했었는데 책에서 중간이나 맨 앞의 변수에도 사용할 수 있다는 것을 알게 되었다.

numbers = [1, 2, 3, 4, 5, 6]
numbers_descending = sorted(numbers, reverse=True)
first, *others, last = number_descending
print(first, others, last)

>>>
1, [2, 3, 4, 5], 6

 

14. 복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라

sort 메서드의 key 파라미터를 사용하면 리스트의 각 원소 대신 비교에 사용할 객체를 반환하는 도우미 함수를 제공할 수 있다.

간단한 sort는 자주 사용했지만 더 복잡한 형식의 sort를 사용해야 하는 경우에는(책에 나온 예시처럼 리스트 안에 있는 객체를 처리하는 경우) for문을 사용하여 객체에 하나씩 접근하는 식으로 작업을 했던 것 같은데 key를 사용하여 더 간단하게 할 수 있다는 것을 알게 되었다. 파이썬의 세계는 무궁무진하고 아직 몰라서 안써본 기능들도 많은 것 같다. 차근차근 배우고 조금씩 활용해봐야겠다.

class Food:
	def __init__(self, name, calorie):
    	self.name = name
        self.calorie = calorie
        
    def __repr__(self):
    	return f'Food({self.name}, {self.weight})'
        

foods = [
	Food('닭가슴살', 300),
    Food('양꼬치', 500),
    Food('샐러드', 200),
    Food('곤약', 100)
   ]
   
foods.sort(key=lambda x: x.name)
foods.sort(key=lambda x: x.name)
print('이름순 정렬:', foods)
print('칼로리순 정렬:', foods)


>>>
이름순 정렬: [Food('곤약', 100), Food('닭가슴살', 300), Food('샐러드', 200), Food('양꼬치', 500)]
칼로리순 정렬: [Food('곤약', 100), Food('샐러드', 200), Food('닭가슴살', 300), Food('양꼬치', 500)]