본문 바로가기

Python/Basic

Python - List Comprehension

*바쁘신 분들을 위한 3줄 요약

1. List생성 code를 한 줄로 표현 가능한 문법입니다.

2. 어려워 보이지만 사실상 python의 문법을 거의 유지하고 있습니다.

3. 속도측면에서 유리할 때도 있지만 주의해서 사용해야 합니다.

 


Introduce(소개)

List Comprehension를 한국어로 직역하면 "목록 표현식"이라 할 수 있습니다. python에서는 List 객체를 한 줄로 표현할 수 있는 고유한 문법을 뜻합니다. 이 문법을 사용하면 List를 작성할 때 보다 간결하게 작성이 가능합니다.

 

예를 들어, 0~9까지의 수 중, 3의 배수가 아닌 것만 list에 담고 싶을 때, 기존 방식으로는 이렇게 작성합니다.

a = []
for i in range(10):
    if i % 3 != 0:
        a.append(i)
print(a) 	## [1, 2, 4, 5, 7, 8]

 

하지만 만약 List Comprehension 문법을 사용한다면 한 줄로 표현이 가능합니다.

b = [x for x in range(10) if x % 3 != 0]
print(b) 	## [1, 2, 4, 5, 7, 8]

 

List Comprehension을 처음 접하면 어떻게 사용해야 하는지 어려울 수 있습니다. 하지만 기능들을 하나씩 살펴보면 기존 python과 다를 바가 없다는 것을 알게 됩니다. 위의 예제를 반복문과 조건문으로 나눠보면 더욱 확실합니다.

## list comprehension를 기능별로 분리
b = [x                      	## 선언
     for x in range(10)     	## 반복문
         if x % 3 != 0      	## 조건문
             # b.append(x)   	## 앞에서 선언했으므로 생략
         ]

 

list comprehension의 다양한 작성법을 몇가지 예제를 통해 알아봅시다.

 

 


Example(예제)

List comprehension 문법을 사용한 것을 a1, 사용하지 않는 것을 a2로 표시합니다.

직접 확인해 보면서 따라하시는 것을 추천합니다.

 

1. 반복문

a1 = [x for x in range(10)]

a2 = []
for i in range(10):
    a2.append(i)

 

2. 반복문, 조건문

a1 = [x for x in range(10) if x % 3 != 0]

a2 = []
for i in range(10):
    if i % 3 != 0:
        a2.append(i)

 

3. 반복문, 산술 연산

a1 = [x**2 for x in range(5)]

a2 = []
for i in range(5):
    a2.append(i**2)

 

3. 반복문, 비교 연산

a1 = [x % 3 != 0 for x in range(10)]

a2 = []
for i in range(10):
    a2.append(i % 3 != 0)

 

4. 반복문에 변수 여러개

a1 = [x*y for x, y in zip(range(10), reversed(range(10)))]

a2 = []
for i, j in zip(range(10), reversed(range(10))):
    a2.append(i * j)

* zip 함수를 활용했습니다.

 

5. 이중 반복문

a1 = [(x, y) for x in range(4) for y in range(4)]

a2 = []
for i in range(4):
    for j in range(4):
        a2.append((i, j))

* x, y를 둘 다 출력하고 싶을 때는 "[x, y for ..."로 하면 안됩니다. "[(x, y) for ..."처럼 하나로 묶어야 합니다.

 

6. 반복문, 반복문, 조건문, 조건문

a1 = [(x, y) for x in range(5) for y in range(10) if y // 3 == 0 if x // 2 == 0]

a2 = []
for i in range(5):
    for j in range(10):
        if j // 3 == 0:
            if i // 2 == 0:
                a2.append((i, j))

 

7. 2부터 1001까지의 소수 list 구하기 예제

a1 = [x for x in range(2, 1001) if all(x % y != 0 for y in range(2, int(x**0.5) + 1))]

a2 = []
for i in range(2, 1001):
    is_prime = True
    for j in range(2, int(i**0.5) + 1):
        if i % j == 0:
            is_prime = False
            break
    if is_prime:
        a2.append(i)

 

 


Efficiency(효용성)

List comprehension은 2가지 장점이 있습니다.
1. 코드의 간결함

2. 속도...?

 

List comprehension 문법에 익숙해지면 코드를 짧고 간결하게 작성할 수 있습니다. 이는 코드의 유지보수 측면에 큰 이득입니다. 뿐만 아니라 몇가지 Case에서는 속도측면에서도 이득입니다. 아래는 timeit 모듈을 사용한 속도 test입니다.

import timeit

## using list comprehension
t1 = timeit.timeit("""
a = list(range(1, 1000001))
b = list(range(1, 1000001))
c = [x*y for x,y in zip(a,b)]
""", number=100)

## using for loop
t2 = timeit.timeit("""
a = list(range(1, 1000001))
b = list(range(1, 1000001))
c = []
for i in range(len(a)):
    c.append(a[i]*b[i])
""", number=100)

print("timeit module using in python 3.11.1 64-bit")
print("Using list comprehension:", t1)	## 12.536577999999281
print("Using for loop:", t2)         	## 18.54819580000185

 

List comprehension을 사용하자 18초에서 12초로 시간이 감소, 약 30% 이상의 시간이 절약되었습니다.

다음 예제도 속도가 줄어드는지 확인해 보겠습니다.

import timeit

## using list comprehension
t1 = timeit.timeit("""
a1 = [x for x in range(2, 10001) if all(x % y != 0 for y in range(2, int(x**0.5) + 1))]
""", number=100)

## using for loop
t2 = timeit.timeit("""
a2 = []
for i in range(2, 10001):
    is_prime = True
    for j in range(2, int(i**0.5) + 1):
        if i % j == 0:
            is_prime = False
            break
    if is_prime:
        a2.append(i)
""", number=100)

print("timeit module using in python 3.11.1 64-bit")
print("Using list comprehension:", t1)	## 5.202187000002596
print("Using for loop:", t2)          	## 1.25099629999022

 

음?? 이번에는 list comprehension이 느립니다. for loop보다 약 4배 시간이 걸렸습니다. 어떻게 된 일일까요?

 

list comprehension를 사용하면 조건문 최적화가 어렵습니다. 특히 위의 예제에서 all 함수가 list comprehension에 불필요한 연산을 일으킵니다. for loop에서의 break가 없다고 생각하면 됩니다.

실제로, for loop 예제에서 break를 없애면 list comprehension과 비슷한 속도를 가집니다.

import timeit

## using list comprehension
t1 = timeit.timeit("""
a1 = [x for x in range(2, 10001) if all(x % y != 0 for y in range(2, int(x**0.5) + 1))]
""", number=100)

## using for loop
t2 = timeit.timeit("""
a2 = []
for i in range(2, 10001):
    is_prime = True
    for j in range(2, int(i**0.5) + 1):
        if i % j == 0:
            is_prime = False

    if is_prime:
        a2.append(i)
""", number=100)

print("timeit module using in python 3.11.1 64-bit")
print("Using list comprehension:", t1)  	## 5.118708599999081
print("Using for loop:", t2)              	## 5.222678500009351

 

break를 없앴더니 속도가 거의 동일합니다. 이는 List comprehension과 for loop가 조건문과 같이 복잡한 동작에서는 큰 속도차이가 없다는 것을 의미합니다. 

 

제대로 속도 이득을 보려면 list 대신 numpy를 사용해보시기 바랍니다.

import timeit

count = 1_000_000


## using list comprehension
start = timeit.default_timer()
list_square = [x**2 for x in range(count)]
end = timeit.default_timer()
print("Using list comprehension: ", end-start)	## 0.08278729999437928 


## using numpy
start = timeit.default_timer()
numpy_square = np.square(np.arange(count))
end = timeit.default_timer()
print("Using numpy: ", end-start)             	## 0.0017503999988548458

 

numpy의 내장함수(np.square)를 활용하면 약 50배의 속도 차이를 체감할 수 있습니다. (numpy를 활용했으므로 return이 list가 아닌 np.array type입니다.)

 


List comprehension 문법은 가독성 측면에서 큰 위력을 발휘합니다. 익숙해 지신다면 python 프로그래밍에 큰 도움이 되실 겁니다.

 


* reference

https://peps.python.org/pep-0202/