Python3 Comprehension

Johnywhisky·2021년 7월 17일
0

TIL

목록 보기
1/9

Data type of python

Comprehension을 이해하기에 앞서 파이썬의 data type에 대해 알고 넘어가야 한다. python의 data type을 나누는 기준 중 하나는 iterable이다. 즉, 요소 하나를 차례대로 반환 가능한 객체라면 iterable type인데 이러한 특징을 가지는 data type은 대표적으로 list, set, tuple, string, dict이 있다. iterable data의 대표적인 특징은 다음의 코드처럼 for 문에서 사용 가능하다는 것이다.

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new_nums = []
for num in nums:
    new_nums.append(num+1)

Comprehension

Comprehension의 사전적 정의는 이해, 내포 정도로 통한다. 그렇다면 파이썬에서는?

Create new iterable data from iterable data

iterable한 데이터에서 새로운 데이터를 만들어내는 방법이다. 우선 다음과 같이 nums라는 list가 있고 새로운 list new_nums는 기존 nums의 각 요소에 1을 더한 값을 저장하고 싶다면 다음과 같이 코드를 작성할 수 있다.

nums = range(1,101)
new_nums = []
for num in nums:
    new_nums.append(num+1)

여기서 동일한 결과를 만들어내는 또 다른 방법이 바로 comprehension이며 다음과 같이 표현할 수 있다.

new_nums = [num+1 for num in nums]
위 코드처럼 새로운 객체를 list로 만들면 list comprehension이 되고 set, dict 또는 다른 어떤 iterable한 객체로 만들지에 따라 괄호를 정해주면 된다.

그나마 가장 난해한 dict comprehension을 이해하면 끝!

dict type도 iterable data type이라 했는데 그렇다면 dict comprehension은 어떻게 표현될까?
먼저 다음과 같은 old_dict가 있을 때 dict 자체도 iterable하기 때문에 바로 comprehension에 사용할 수 있다.

old_dict = {
    "가" : "a",
    "나" : "b",
    "다" : "c",
    "라" : "d"
    }

hash_dict = {
    x:hash(x) for x in old_dict
    }
# 결과 : {'가': 1649894707961073160,
#	 '나': -7817533552562337282,
#	 '다': 6955986961735231885,
# 	 '라': -1007652265469636599}

여기서 알 수 있는 점은 기존의 dict에서 추출되는 값은 key만 나온다는 것이다.

hash_dict = {
    x:hash(y) for x,y in old_dict
    }
# Error 발생!

위와 같은 코드를 실행하면 에러가 발생할 것이다. 그렇다면 다음과 같이 key와 value의 위치를 바꿔서 다음과 같은 형태로 만들고 싶다면 어떻게 코드를 짜야할까?

old_dict = {		 =======>	new_dict = {
    "가" : "a",		"바꾸고싶다!" 	   	    'a': '가',
    "나" : "b",			  		    'b': '나',
    "다" : "c",			 		    'c': '다',
    "라" : "d"					    'd': '라'
    }				 		    }

dict의 method 중 .items()를 이용해 새로운 new_dict에 위치만 바꿔서 저장해주면 간단하게 할 수 있다.

new_dict = {
    val:key for key,val in old_dict.items()
    }

if else in comprehension

dict comprehension 다음으로 난해한 산은 if else가 포함되어있는 comprehension이다. 다음과 같은 문제를 생각해보자.

1부터 100까지 자연수 배열이 주어질 때
7의 배수 중 짝수는 1을 더하고 홀수는 그대로 저장한 배열

for문을 이용해 문제를 해결하면 다음과 같이 풀 수 있다.

nums = range(1,101)
for num in nums:
    if not (num % 7):
        if not (num % 2):
            new_nums.append(num+1)
        else:
            new_nums.append(num)

이것을 comprehension으로 풀면 다음과 같이 풀 수 있다.

new_nums = [
    num + 1 if not (num % 2) else num
    for num in nums if not (num % 7)
    ]

복잡해 보이지만 정리하면 아래의 사진과 같다.

Nested for loop의 comprehension

다음과 같은 이중 포문을 생각해보자

num1 = range(1,10)
num2 = range(100,901,100)
new_nums = []
for i in num1:
    for j in num2:
        new_nums.append(i + j)
# 결과 >> 
# new_nums = [
#     101, 201, 301, 401, 501, 601, 701, 801, 901,
#     102, 202, 302, 402, 502, 602, 702, 802, 902,
#     103, 203, 303, 403, 503, 603, 703, 803, 903,
#     104, 204, 304, 404, 504, 604, 704, 804, 904,
#     105, 205, 305, 405, 505, 605, 705, 805, 905,
#     106, 206, 306, 406, 506, 606, 706, 806, 906,
#     107, 207, 307, 407, 507, 607, 707, 807, 907,
#     108, 208, 308, 408, 508, 608, 708, 808, 908,
#     109, 209, 309, 409, 509, 609, 709, 809, 909
#     ]

이러한 이중 포문을 comprehension으로 나타내면 다음과 같다.

num1 = range(1,10)
num2 = range(100,901,100)
new_nums = [i + j for i in num1 for j in num2]

그런데 이거 왜 써야하죠?

지금까지 많은 경우의 comprehension 표현식을 봤는데 왜? 써야 할까? 우선 퍼포먼스 차이에 있다. 어느 유튜브에서 자주 하는 말 중 하나가 '극단적인 상황을 생각해보자'는 건데, 똑같이 극단적인 상황에서 comprehension은 for 문을 이용한 표현식 보다 조금 더 나은 퍼포먼스를 보여준다.
다음의 예시와 같이 1부터 1억까지 배열의 모든 요소에 1씩 더해 새로운 배열을 만든다고 할 때 두 방식의 소요 시간은 약 5초 정도 차이가 나는 걸 확인할 수 있었다.

nums = range(1,100000001)
# For Loop 
start = datetime.now()
new_nums = []
for num in nums:
    new_nums.append(num+1)
end = datetime.now() 
print(end-start) # > 결과 : 0:00:13.450069

# Comprehension
start = datetime.now()
new_nums = [num+1 for num in nums]
end = datetime.now()
print(end-start) # 0:00:08.492588

구글링해보니 .append의 method 수행 과정에서 퍼포먼스 저하가 나타난다고 한다. 그렇기 때문에 많은 상황에서 comprehension을 사용하는 걸 추천드린다!

profile
안녕하세요 :) 1년 차 Pythonist 백엔드 개발자 윤서준입니다.

0개의 댓글