-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create 4. Comprehensions and Generators.md (#151)
- Loading branch information
Showing
1 changed file
with
212 additions
and
0 deletions.
There are no files selected for viewing
212 changes: 212 additions & 0 deletions
212
hong/python/effective-python/4. Comprehensions and Generators.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
# Chapter 4. Comprehensions and Generators | ||
|
||
### 컴프리헨션(Comprehension) (Better Way 27-29): | ||
|
||
- 많은 프로그램이 리스트 딕셔너리의 키/값 쌍, 집합 처리를 중심으로 만들어진다. | ||
- 파이썬에서는 컴프리헨션이라는 특별한 구문을 사용해 이런 (리스트, 딕셔너리, 집합 등) 타입을 간결하게 이터레이션하면서 원소로부터 파생되는 데이터 구조를 생성할 수 있다. | ||
- 컴프리헨션을 사용하면 이런 타입에 대해 일반적인 작엄을 수행하는 코드의 가독성을 높일 수 있고 몇 가지 다른 이점도 얻을 수 있다. | ||
|
||
### 제너레이터(Generator) (Better Way 30-35): | ||
|
||
- 컴프리헨션 코딩 스타일은 제너레이터를 사용한느 함수로 확장할 수 있다. | ||
- 제너레이터는 함수가 점진적으로 반환하는 값으로 이뤄지는 스트림을 만들어준다. | ||
- 이터레이터를 사용할 수 있는 곳 (for 루프, 별표 식 등)이라면 어디에서나 제너레이터 함수를 호출한 결과를 사용할 수 있다. | ||
- **제너레이터를 사용하면 성능을 향상시키고, 메모리 사용을 줄이고, 가독성을 높일 수 있다.** | ||
|
||
## Better Way 27: map과 filter 대신 컴프리헨션을 사용하라 | ||
|
||
```python | ||
# map과 filter 사용 | ||
a = [1,2,3,4,5,6,7,8,9,10] | ||
alt_dict =dict(map(lambda x: (x, x**2), filter(lambda x: x % 2 == 0, a))) | ||
alt_set = set(map(lambda x: x**3, filter(lambda x: x % 3 == 0, a))) | ||
|
||
# 리스트 컨프리헨션 | ||
even_squares = [x**2 for x in a if x % 2 == 0] | ||
# 딕셔너리 컴프리헨션 | ||
even_squares_dict = {x: x**2 for x in a if x % 2 == 0} | ||
print(even_squares_dict) # Output: {2: 4, 4: 16, 6: 36, 8: 64, 10: 100} | ||
# 집합 컴프리헨션 | ||
three_cubed_set = {x**3 for x in a if x % 3 == 0} | ||
print(threes_cubed_set) # Output: {216, 729, 27} | ||
``` | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 리스트 컴프리헨션은 lambda 식을 사용하지 않기 때문에 같은 일을 하는 map과 filter 내장함수를 사용한느 것보다 더 명확하다. | ||
- 리스트 컴프리헨션을 사용하면 쉽게 입력 리스트의 원소를 건너뛸 수 있다. 하지만 map을 사용하는 경우에는 filter의 도움을 받아야만 한다. | ||
- 딕셔너리와 집합도 컴프리헨션으로 생성할 수 있다. | ||
|
||
## Better Way 28: 컴프리헨션 내부에 제어 하위 식을 세 개 이상 사용하지 말라 | ||
|
||
컴프리헨션은 기본적인 사용법(Better Way 27 참고) 외에도 루프를 여러 수준으로 내포하도록 허용한다. | ||
|
||
컴프리헨션에 하위 식을 두 개 포함시키면 각각의 하위식은 컴프리헨션에 들어간 순서대로 왼쪽에서 오른쪽으로 실행된다. | ||
|
||
```python | ||
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] | ||
flat = [x for row in maxtrix for x in row] | ||
print(flat) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9] | ||
``` | ||
|
||
다중 루프 사용이 타당한 다른 예로는 2단계 깊이로 구성된 입력 list 구조를 복제하는 경우를 들 수 있다. | ||
|
||
예를 들어 이차원 행렬의 원소를 제곱하고 싶다고 하자. 이 컴프리헨션은 []가 더 들어가야 하므로 잡음이 좀 더 많지만 여전히 읽기 쉬운 편이다. | ||
|
||
```python | ||
squared = [[x**2 for x in row] for row in matrix] | ||
print(squared) # Output: [[1, 4, 9], [16, 25, 36],[49, 64, 81]] | ||
``` | ||
|
||
만약 이런 컴프리헨션 안에 다른 루프가 들어 있으면 코드가 너무 길어져서 여러 줄로 나눠 서야 한다. | ||
|
||
이 정도가 되면 다중 컴프리헨션이 다른 대안에 비해 더 길어진다. | ||
|
||
이런 경우에는 들여쓰기를 사용해 3단계 리스트 컴프리헨션보다 더 명확하게 코드를 작성할 수 있다. | ||
|
||
```python | ||
flat = [] | ||
for sublist1 in my_lists: | ||
for sublist2 in sublist1: | ||
flat.extend(sublist2) | ||
``` | ||
|
||
컴프리헨션은 여러 if 조건을 허용한다. 여러 조건을 같은 수준의 루프에 사용하면 암시적으로 and 식을 의미한다. 예를 들어 숫자로 이뤄진 리스트에서 4보다 큰 짝수만 남기고 싶다고 하자. 다음 두 리스트 컴프리헨션이 같은 역할을 한다. | ||
|
||
```python | ||
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | ||
b = [x for x in a if x > 4 if x % 2 == 0] | ||
c = [x for x in a if x > 4 and x % 2 == 0] | ||
``` | ||
|
||
각 수준의 for 하위 식의 바로 뒤에 if를 추가함으로써 각 수준마다 조건을 지정할 수 있다. | ||
|
||
컴프리 헨션에 들어간느 하위 식이 세 개 이상 되지 않게 제한하라는 규칙을 지켜라. | ||
|
||
즉, 조건문 두 개, 루프 두 개, 혹은 조건문 한 개와 루프 한 개를 사용할 수 있다는 뜻이다. | ||
|
||
컴프리헨션이 이보다 더 복잡해지면 일반 if와 for문을 사용하고 도우미 함수를 작성하라. | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 컴프리헨션은 여러 수준의 루프를 지원하며 각 수준마다 여러 조건을 지원한다. | ||
- 제어 하위 식이 세 개 이상인 컴프리헨션은 이해하기 매우 어려우므로 가능하면 피해야 한다. | ||
|
||
## Better Way 29: 대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라 | ||
|
||
컴프리헨션(리스트, 딕셔너리, 집합 중 무엇이든)에서 같은 계산을 여러 위치에서 공유하는 경우가 흔하다. | ||
|
||
익셔너리 컴프리헨션을 사용하면 루프의 로직을 더 간결하게 표현할 수 있다. | ||
|
||
```python | ||
found = {name: get_batches(stock.get(name, 0}, 8) | ||
for name in order | ||
if get_batches(stock.get(name, 0), 8)} | ||
print(found) # Output: {'나사못': 4, '나비너트': 1} | ||
``` | ||
|
||
이 코드는 `get_batches(stock.get(name, 0), 8)`이 반복된다는 단점이 있다. 이로 인해 기술적으로는 불필요한 시각적인 잡음이 들어가서 가독성이 나빠진다. 그리고 두 식을 항상 똑같이 변경해야 하므로 실수할 가능성도 높아진다. | ||
|
||
이러한 문제에 대한 쉬운 해결 방법은 파이썬 3.8에 도입된 왈러스 연산자(:=)를 사용하는 것이다. 왈러스 연산자를 사용하면 컴프리헨션의 일부분에 대입식을 만들 수 있다. | ||
|
||
```python | ||
found = {name: batches for name in order | ||
if (batches := get_batches(stock.get(name, 0), 8))} | ||
``` | ||
|
||
대입식(`batches := get_batches(…)` )을 사용하면 stock 딕셔너리에서 각 order 키를 한 번만 조회하고 get_batches를 한 번만 호출해서 그 결과를 batches 변수에 저장할 수 있다. 컴프리헨션의 다른 곳에서는 batches 변수를 참조해서 get_batches를 다시 호출할 필요 없이 딕셔너리의 내용을 만들 수 있다. `get_batches` 를 얻기 위한 불필요한 함수 호출을 제거하면 order 리스트 안에 있는 각 요소에 대해 불필요한 연산을 수행하지 않으므로 성능도 향상된다. | ||
|
||
대입식을 컴프리헨션의 값 식에 사용해도 문법적으로 올바르다. 하지만 컴프리헨션의 다른 부분에서 이 변수를 읽으려고 하면 컴프리헨션이 평가되는 순서 때문에 실행 시점에 오류가 발생할 것이다. | ||
|
||
```python | ||
result = {name: (tenth := count // 10) | ||
for name, count in stock.items() if tenth > 0} | ||
>>> | ||
Traceback ... | ||
NameError: name 'tenth' is not defined | ||
``` | ||
|
||
대입식을 조건 쪽으로 옮기고 대입식에서 만들어진 변수 이름을 컴프리헨션 값 식에서 참조하면 이 문제를 해결할 수 있다. | ||
|
||
```python | ||
result = {name: tenth for name, count in stock.items() | ||
if (tenth := count // 10) > 0} | ||
``` | ||
|
||
루프 변수는 누출하지 않는 편이 낫다. 따라서 컴프리헨션에서 대입식을 조건에만 사용하는 것을 권장한다. | ||
|
||
```python | ||
found = ((name, batches) for name in order | ||
if (batches := get_batches(stock.get(name, 0), 8)) | ||
print(next(found)) | ||
print(next(found)) | ||
|
||
>>> | ||
('나사못', 4) | ||
('나비너트', 1) | ||
``` | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 대입식을 통해 컴프리헨션이나 제너레이터 식의 조건 부분에서 사용한 값을 같은 컴프리헨션이나 제너레이터의 다른 위치에서 재사용할 수 있다. 이를 통해 가독성과 성능을 향상시킬 수 있다. | ||
- 조건이 아닌 부분에도 대입식을 사용할 수 있지만, 그런 형태의 사용은 피해야 한다. | ||
|
||
## Better Way 30: 리스트를 반환하기보다는 제너레이터를 사용하라 | ||
|
||
시퀀스를 결과로 만들어내는 함수를 만들 때 가장 간단한 선택은 원소들이 모인 리스트를 반환하는 것이다. | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 제너레이터를 사용하면 결과를 리스트에 합쳐서 반환하는 것보다 더 깔끔하다. | ||
- 제너레이터가 반환하는 이터레이터는 제너레이터 함수의 본문에서 yield가 반환한느 값들로 이뤄진 집합을 만들어낸다. | ||
- 제너레이터를 사용하면 작업 메모리에 모든 입력과 출력을 저장할 필요가 없으므로 입력이 아주 커도 출력 시퀀스를 만들 수 있다. | ||
|
||
## Better Way 31: 인자에 대해 이터레이션할 때는 방어적이 돼라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 입력 인자를 여러 번 이터레이션하는 함수나 메서드를 조심하라. 입력받은 인자가 이터레이터면 함수가 이상하게 작동하거나 결과가 없을 수 있다. | ||
- 파이썬의 이터레이터 프로토콜은 컨테이너와 이터레이터가 iter, next 내장 함수나 for 루프 등의 관련 식과 상호작용하는 절차를 정의한다. | ||
- __iter__ 메서드를 제너레이터로 정의하면 쉽게 이터러블 컨테이너 타입을 정의할 수 있다. | ||
- 어떤 값이 (컨테이너가 아닌) 이터레이터인지 감지하려면, 이 값을 iter 내장 함수에 넘겨서 반환되는 값이 원래 값과 같은지 확인하면 된다. 다른 방법으로 collections.abc.Iterator 클래스를 isInstance와 함께 사용할 수도 있다. | ||
|
||
## Better Way 32: 긴 리스트 컴프리헨션보다는 제너레이터 식을 사용하라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 입력이 크면 메모리를 너무 많이 사용하기 때문에 리스트 컴프리헨션은 문제를 일으킬 수 있다. | ||
- 제너레이터 식은 이터레이터처럼 한 번에 원소를 하나씩 출력하기 때문에 메모리 문제를 피할 수 있다. | ||
- 제너레이터 식이 반환한 이터레이터를 다른 제너레이터 식의 하위 식으로 사용함으로써 제너레이터 식을 서로 합성할 수 있다. | ||
- 서로 연결된 제너레이터 식은 매우 빠르게 실행되며 메모리도 효율적으로 사용한다. | ||
|
||
## Better Way 33: yield from을 사용해 여러 제너레이터를 합성하라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- yield from 식을 사용하면 여러 내장 제너레이터를 모아서 제너레이터 하나로 합성할 수 있다. | ||
- 직접 내포된 제너레이터를 이터레이션하면서 각 제너레이터의 출력을 보내느 것보다 yield from을 사용하는 것이 성능 면에서 더 좋다. | ||
|
||
## Better Way 34: send로 제너레이터에 데이터를 주입하지 말라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- send 메서드를 사용해 데이터를 제너레이터에 주입할 수 있다. 제너레이터는 send로 주입된 값을 yield 식이 반환하는 값을 통해 받으며, 이 값을 변수에 저장해 활용할 수 있다. | ||
- send와 yield from 식을 함께 사용하면 제너레이터의 출력에 None이 불쑥불쑥 나타나는 의외의 결과를 얻을 수도 있다. | ||
|
||
## Better Way 35: 제너레이터 안에서 throw로 상태를 변화시키지 말라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- throw 메서드를 사용하면 제너레이터가 마지막으로 실행한 yield 식의 위치에서 예외를 다시 발생시킬 수 있다. | ||
- throw를 사용하면 가독성이 나빠진다. 예외를 잡아내고 다시 발생시키는 데 준비 코드가 필요하며 내포 단계가 깊어지기 때문이다. | ||
- 제너레이터에서 예외적인 동작을 제공하는 더 나은 방법은 __iter__ 메서드를 구현하는 클래스를 사용하면서 예외적인 경우에 상태를 전이시키는 것이다. | ||
|
||
## Better Way 36: 이터레이터나 제너레이터를 다룰 때는 itertools를 사용하라 | ||
|
||
### 기억해야 할 내용 | ||
|
||
- 이터레이터나 제너레이터를 다루는 itertools 함수는 세 가지 범주로 나눌 수 있다. | ||
- 여러 이터레이터를 연결함 | ||
- 이터레이터의 원소를 걸러냄 | ||
- 원소의 조합을 만들어냄 | ||
- 파이썬 인터프리터에서 help(itertools)를 입력한 후 표시되는 문서를 살펴보면 더 많은 고급 함수와 추가 파라미터를 알 수 있으며, 이를 사용하는 유용한 방법도 확인할 수 있다. |