[Effective Python] 4. Comprehensions and Generators

Stop._.bmin·2023년 1월 28일
0

Effective-python

목록 보기
4/5
post-custom-banner

Item 27: Use Comprehensions Instead of map and filter

원래는 특정 리스트가 있을 때 square한 값을 리스트에 채워넣으려고 하면 그냥 또다른 리스트를 생성했었는데 필자는 아래와 같은 방법을 제시했다.

squares = [x**2 for x in a] # list comprehension
print(squares)
alt = map(lambda x: x**2, a)

맵을 활용해서도 이를 구현하였다.
그리고 또한 comprepension에 conditional loop을 넣을 수 있는데

even_squares = [x**2 for x in a if x % 2 ==0]

위와 같은코드를 작성할 수 있다

filter

필터를 활용하여 위에 있는 예시 코드를 맵 함수로 구현할 수 있다.

alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a))
assert even_squares == list(alt)

또한 딕셔너리를 통해서도 할 수 있다

even_squares_dict = {x: x**2 for x in a if x % 2 == 0}
threes_cubed_set = {x**3 for x in a if x % 3 == 0}
print(even_squares_dict)
print(threes_cubed_set)
>>>
{2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
{216, 729, 27}

모든 결과는 동일하게 맵과 필터를 이용한다면 그런데 좀 길고 지저분할 수 있다. 그래서 그것을 피해야한다.

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)))

Avoid More Than Two Control Subexpressions in Comprehensions

  • 두 개 이상의 제어 하위 표현식을 피하자
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

가령 위의 코드는 가독성이 좋은 반면, 아래의 코드는 조금 지저분하지만 그래도 상대적으로 읽기 좋다.

squared = [[x**2 for x in row] for row in matrix]
print(squared)
>>>
[[1, 4, 9], [16, 25, 36], [49, 64, 81]]

만약에 comprehension에 또다른 루프문이 있다면, 더 길어지기에 나눌 필요가 있을 것이다. 가령 아래의 코드를 보자면

my_lists = [
    [[1, 2, 3], [4, 5, 6]],
    ...
]
flat = [x for sublist1 in my_lists
        for sublist2 in sublist1
        for x in sublist2]

위의 코드를 다음과 같이 정리하는 것이다.

flat = []
for sublist1 in my_lists:
    for sublist2 in sublist1:
        flat.extend(sublist2)

결론

  • 컴프리헨션은 여러 수준의 루프와 루프 수준당 여러 조건을 지원한다.
  • 두 개 이상의 제어 하위 표현식이 있는 컴프리헨션은 읽기가 매우 어려우므로 피해야 한다.

Item 29: Avoid Repeated Work in Comprehensions by Using Assignment Expressions

  • 여러 반복되는 코드들을 앞 장에서 배웠던 walrus 연산자(:=0)을 통해서 해결하는 것이다.
found = {name: batches for name in order
         if (batches := get_batches(stock.get(name, 0), 8))}

정리

  • 할당 표현식을 사용하면 컴프리헨션과 제너레이터 표현식이 동일한 컴프리헨션의 다른 곳에서 한 조건의 값을 재사용할 수 있으므로 가독성과 성능이 향상될 수 있다.
  • Comprehension이나 Generator 표현식의 조건이 아닌 대입 표현식을 사용할 수 있지만, 그렇게 하는 것은 피해야 한다.

Item 30: Consider Generators Instead of Returning Lists

def index_words(text):
    result = []
    if text:
        result.append(0)
    for index, letter in enumerate(text):
        if letter == ' ':
            result.append(index + 1)
    return result

위의 코드는 2개의 문제가 있는데

  1. 코드가 너무 집약적이고 지저분하다
    따라서 아래와 같이 코드를 짬으로서 해결할 수 있다. (generator)
def index_words_iter(text):
    if text:
        yield 0
    for index, letter in enumerate(text):
        if letter == ' ':
            yield index + 1
  1. 모든 결과를 리스트에 리턴되기 전에 미리 다 저장해야 한다는 점이다
    이를 해결하기 위해 아래의 코드를 제시해주었다
def index_file(handle):
    offset = 0
    for line in handle:
        if line:
            yield offset
        for letter in line:
            offset += 1
            if letter == ' ':
yield offset

정리

  • 함수가 누적된 결과 목록을 반환하는 것보다 생성기를 사용하는 것이 더 명확할 수 있다.
  • 제너레이터가 반환한 이터레이터는 제너레이터 함수 본문 내에서 yield 표현식에 전달된 값 세트를 생성한다.
  • 제너레이터는 작업 메모리가 모든 입력과 출력을 포함하지 않기 때문에 임의로 큰 입력에 대해 일련의 출력을 생성할 수 있다.

Item 31: Be Defensive When Iterating Over Arguments

만약 함수가 리스트를 parameter로 갖게 된다면 리스트가 여러변 iterate된다는 것이 중요하다.
그래서 따로 함수를 미리 복사해두는 것이 더 효율적이다.

정리

  • 입력 인수를 여러 번 반복하는 함수 및 메서드에 주의하자. 이러한 인수가 반복자이면 이상한 동작과 누락된 값이 나타날 수 있다.
  • Python의 반복자 프로토콜은 컨테이너와 반복자가 iter 및 next 내장 함수, for 루프 및 관련 표현식과 상호 작용하는 방식을 정의한다.
  • iter 메서드를 생성기로 구현하여 자신만의 반복 가능한 컨테이너 유형을 쉽게 정의할 수 있다.
  • 값에 대해 iter를 호출하면 전달한 것과 동일한 값이 생성되는 경우 값이 (컨테이너 대신) 반복자임을 감지할 수 있다. 또는 collections.abc.Iterator와 함께 isinstance 내장 함수를 사용할 수 있다.
profile
원하는 만큼만
post-custom-banner

0개의 댓글