Python에서 목록 이해(list comprehension)를 사용해야 하는 경우

2024. 1. 2. 07:54python/basic

  • 루프 및 map() 호출을 Python의 목록 이해로 다시 작성
  • 컴프리헨션, 루프 및 map() 호출 중에서 선택
  • 조건부 논리로 이해력을 강화하세요.
  • filter() 교체 컴프리헨션을 사용
  • 성능 문제를 해결하기 위해 코드 프로파일 하기

Python에서 목록을 만드는 방법

for 루프 사용

  1. 빈 list를 인스턴스화합니다.
  2. 반복 가능한 요소 또는 요소의 범위를 반복합니다.
  3. 추가 각 요소를 list 끝에 추가합니다.
In [ ]:
squares = []
for i in range(10):
    squares.append(i * i)

squares

map() 개체 사용

거래 목록에 대한 세후 가격을 계산해야 하는 상황을 생각해 보십시오.

In [ ]:
txns = [1.09, 23.56, 57.84, 4.56, 6.78]
TAX_RATE = .08
def get_price_with_tax(txn):
    return txn * (1 + TAX_RATE)

final_prices = map(get_price_with_tax, txns)

list(final_prices)

여기에는 반복 가능한 txns 함수와 get_price_with_tax() 함수가 있습니다. 이 두 인수를 모두 map()에 전달하고 결과 개체를 final_prices에 저장합니다.

list comprehension(목록 이해) 사용

In [ ]:
squares = [i * i for i in range(10)]
squares

빈 list를 만들고 각 요소를 끝에 추가하는 대신 다음 형식에 따라 list과 해당 내용을 동시에 정의하기만 하면 됩니다.

new_list = [expression for member in iterable]

Python의 모든 목록 이해에는 세 가지 요소가 포함됩니다.

  • expression은 멤버 자체, 메서드 호출 또는 값을 반환하는 기타 유효한 표현식입니다. 위의 예에서 i * i 표현식은 멤버 값의 제곱입니다.
  • member은 list이거나 반복 가능한 개체 또는 값입니다. 위의 예에서 멤버 값은 i 입니다.
  • iterable은 해당 요소를 한 번에 하나씩 반환할 수 있는 list, set, sequence, generator 또는 다른 개체입니다. 위의 예에서 iterable은 range(10)입니다.
In [ ]:
txns = [1.09, 23.56, 57.84, 4.56, 6.78]
TAX_RATE = .08

def get_price_with_tax(txn):
    return txn * (1 + TAX_RATE)

final_prices = [get_price_with_tax(i) for i in txns]

final_prices

목록 이해 사용의 이점

Python에서 목록 이해를 사용하는 주요 이점 중 하나는 이것이 다양한 상황에서 사용할 수 있는 단일 도구라는 것입니다. 표준 목록 생성 외에도 목록 이해를 매핑 및 필터링에 사용할 수도 있습니다. 각 시나리오마다 다른 접근 방식을 사용할 필요는 없습니다.

이것이 왜 목록 이해가 Pythonic으로 고려되는 주된 이유입니다. Python은 다양한 용도로 사용할 수 있는 간단하고 강력한 도구를 수용합니다. 추가적인 이점으로, Python에서 목록 이해를 사용할 때마다 map()를 호출할 때처럼 인수의 적절한 순서를 기억할 필요가 없습니다.

목록 이해는 루프보다 더 선언적이므로 읽고 이해하기가 더 쉽습니다. 루프를 사용하려면 목록이 생성되는 방식에 집중해야 합니다. 수동으로 빈 목록을 만들고, 요소를 반복하고, 각 요소를 목록 끝에 추가해야 합니다. Python의 목록 이해 기능을 사용하면 목록에 들어가고 싶은 무엇에만 집중하고 Python이 어떻게 목록 구성이 이루어질 것이라고 믿을 수 있습니다

당신의 이해력을 강화하는 방법

조건부 논리 사용

이해 공식에 대한 보다 완전한 설명에는 선택적 조건문에 대한 지원이 추가되었습니다. 조건 논리를 목록 이해에 추가하는 가장 일반적인 방법은 표현식 끝에 조건을 추가하는 것입니다.

new_list = [expression for member in iterable (if conditional)]

조건부는 목록 이해를 통해, 원치 않는 값을 필터링할 수 있기 때문에 중요합니다.

In [ ]:
sentence = 'the rocket came back from mars'
vowels = [i for i in sentence if i in 'aeiou']
vowels

조건부는 유효한 표현식을 테스트할 수 있습니다. 더 복잡한 필터가 필요한 경우 조건부 논리를 별도의 함수로 이동할 수도 있습니다.

In [ ]:
sentence = 'The rocket, who was named Ted, came back \
 from Mars because he missed his friends.'
def is_consonant(letter):
    vowels = 'aeiou'
    return letter.isalpha() and letter.lower() not in vowels
    
consonants = [i for i in sentence if is_consonant(i)]
In [ ]:
print(consonants)
In [ ]:
consonants

간단한 필터링을 위해 명령문 끝에 조건문을 배치할 수 있지만 대신 멤버 값을 변경하려는 경우에는 어떻게 해야 합니까이 경우 조건문을 표현식의 시작 근처에 배치하는 것이 유용합니다.

new_list = [expression (if conditional) for member in iterable]

예를 들어 가격 목록이 있는 경우 음수 가격을 0로 바꾸고 양수 값은 변경하지 않고 그대로 둘 수 있습니다.

In [ ]:
original_prices = [1.25, -9.45, 10.22, 3.78, -5.92, 1.16]
prices = [i if i > 0 else 0 for i in original_prices]

prices
In [ ]:
def get_price(price):
    return price if price > 0 else 0

prices = [get_price(i) for i in original_prices]

prices

집합 및 사전 이해 사용

집합 이해는 Python의 목록 이해와 거의 동일합니다. 차이점은 집합 내포가 출력에 중복이 포함되지 않았는지 확인한다는 것입니다. 대괄호 대신 중괄호를 사용하여 집합 이해를 만들 수 있습니다.

In [ ]:
quote = "life, uh, finds a way"
unique_vowels = {i for i in quote if i in 'aeiou'}

unique_vowels

사전 이해는 유사하며 키 정의에 대한 추가 요구 사항은 다음과 같습니다.

In [ ]:
squares = {i: i * i for i in range(10)}

squares

Walrus 연산자 사용

Python 3.8에는 바다코끼리 연산자(walrus)라고도 알려진 할당 표현식이 도입됩니다.

In [ ]:
import random

def get_weather_data():
    return random.randrange(90, 110)
hot_temps = [temp for _ in range(20) if (temp := get_weather_data()) >= 100]

hot_temps

Python에서 목록 이해를 사용하지 말아야 할 경우

중첩된 이해를 조심하세요

In [ ]:
cities = ['Austin', 'Tacoma', 'Topeka', 'Sacramento', 'Charlotte']
temps = {city: [0 for _ in range(7)] for city in cities}

temps

사전 이해 기능을 사용하여 외부 컬렉션을 만듭니다. temps 표현식은 또 다른 이해력을 포함하는 키-값 쌍입니다. 이 코드는 cities의 각 도시에 대한 데이터 list를 빠르게 생성합니다.

중첩 목록은 수학적 목적으로 자주 사용되는 행렬을 생성하는 일반적인 방법입니다.

In [ ]:
matrix = [[i for i in range(5)] for _ in range(6)]

matrix

외부 목록 이해 [... for _ in range(6)]는 6개의 행을 생성하고, 내부 목록 이해 [i for i in range(5)]는 이러한 각 행을 값으로 채웁니다.

In [ ]:
matrix = [
    [0, 0, 0],
    [1, 1, 1],
    [2, 2, 2],
]

flat = [num for row in matrix for num in row]

flat

행렬을 평면화하는 코드는 간결하지만 작동 방식을 이해하는 것이 그리 직관적이지 않을 수 있습니다. 반면에 for 루프를 사용하여 동일한 행렬을 평면화하면 코드가 훨씬 더 간단해집니다.

In [ ]:
matrix = [
    [0, 0, 0],
    [1, 1, 1],
    [2, 2, 2],
]

flat = []

for row in matrix:
   for num in row:
       flat.append(num)

flat

대규모 데이터세트를 위한 생성기 선택

첫 번째 10억 정수의 제곱을 더하고 싶다면 어떻게 해야 할까요? 컴퓨터에서 시도한 경우 컴퓨터가 응답하지 않는 것을 확인할 수 있습니다. 그 이유는 Python이 10억 개의 정수로 구성된 목록을 만들려고 하기 때문입니다. 이는 컴퓨터가 원하는 것보다 더 많은 메모리를 소비합니다.
생성기는 메모리에 하나의 큰 데이터 구조를 생성하지 않고 대신 반복 가능한 데이터 구조를 반환합니다. 코드는 필요한 만큼 반복 가능 항목에서 다음 값을 요청하거나 시퀀스 끝에 도달할 때까지 한 번에 하나의 값만 저장할 수 있습니다.
다음은 1천만 정수를 제곱하는 예 입니다.

In [ ]:
sum(i * i for i in range(10000000))

map()또한 느리게 작동하므로 이 경우 메모리를 사용하도록 선택하면 메모리가 문제가 되지 않습니다.

In [ ]:
sum(map(lambda i: i*i, range(10000000)))

성능 최적화를 위한 프로필

성능이 중요한 시나리오에서는 일반적으로 다양한 접근 방식을 프로파일 분석하고 데이터를 듣는 것이 가장 좋습니다. timeit은 코드 덩어리를 실행하는 데 걸리는 시간을 측정하는 데 유용한 라이브러리입니다. timeit를 사용하여 map(), for 루프 및 목록 이해의 런타임을 비교할 수 있습니다.

In [ ]:
import random
import timeit
TAX_RATE = .08
txns = [random.randrange(100) for _ in range(100000)]

def get_price(txn):
    return txn * (1 + TAX_RATE)

def get_prices_with_map():
    return list(map(get_price, txns))

def get_prices_with_comprehension():
    return [get_price(txn) for txn in txns]

def get_prices_with_loop():
    prices = []
    for txn in txns:
        prices.append(get_price(txn))
    return prices

timeit.timeit(get_prices_with_map, number=100) # map()
In [ ]:
timeit.timeit(get_prices_with_comprehension, number=100) # 목록 이해
In [ ]:
timeit.timeit(get_prices_with_loop, number=100) # for 루프