Q1. 1.다음과 같은 도시목록의 리스트가 주어졌을때, 도시이름이 S로 시작하지 않는 도시만 리스트로 만들 때 리스트 컴프리헨션을 사용하여 함수를 작성해 보세요.
풀이 1 : list_comprehension 사용
cities = ["Tokyo", "Shanghai", "Jakarta", "Seoul", "Guangzhou", "Beijing", "Karachi", "Shenzhen", "Delhi" ]
print([non_s for non_s in cities if "S" not in non_s])
도시이름이 'S'로 시작하기에 "각 요소들의 알파벳 하나하나를 접근해야지"와 같은
고민을 할 수도 있다.
하지만
'S'로 시작한다는 것은 적어도 이 문제에서는 대문자'S'가 있는 단어는 배제하라
와 같은
말이므로 함수가 아닌 list_comprehension으로 진행했다.
풀이 2
이번엔 함수로 구현해보자.
cities = ["Tokyo", "Shanghai", "Jakarta", "Seoul", "Guangzhou", "Beijing", "Karachi", "Shenzhen", "Delhi" ]
def kill_s(cities):
non_s = []
kill_word = 'S'
for each in cities:
temp = list(each)
if temp[0] != kill_word:
non_s.append(each)
return non_s
print(kill_s(cities))
여기서 핵심은 each
를 어떻게 하면 알파벳 단위로('T', 'o', 'k', 'y', 'o')로 접근하느냐 이다.
for each in cities:
# each를 단어를 접근할 수 있게 리스트화!
temp = list(each)
그리고 temp[0]은 단어의 첫 문자이기에
if temp[0] != kill_word:
non_s.append(each)
첫 문장이 'S'( = kill_word)가 아닌 것만 새로운 리스트에 append하였다.
Q2. (도시, 인구수)가 튜플의 리스트로 주어졌을때,
키(key)는 도시
,값이 인구수
인딕셔너리
를 '딕셔너리 컴프리헨션'을 사용한 함수를 작성해 보세요.
population_of_city = [(‘Tokyo', 36923000), (‘Shanghai', 34000000), (‘Jakarta', 30000000), (‘Seoul', 25514000), (‘Guangzhou', 25000000), (‘Beijing', 24900000), (‘Karachi', 24300000 ), ( ‘Shenzhen', 23300000), (‘Delhi', 21753486) ]
풀이1
population_of_city = [('Tokyo', 36923000), ('Shanghai', 34000000), ('Jakarta', 30000000), ('Seoul', 25514000),
('Guangzhou', 25000000), ('Beijing', 24900000), ('Karachi', 24300000), ( 'Shenzhen', 23300000), ('Delhi', 21753486)]
my_dict = dict(population_of_city)
print(my_dict)
사실 단 한줄의 코드를 통해 원하는 데이터가 간단히 나옴을 알 수 있다.
my_dict = dict(population_of_city)
결과
{'Tokyo': 36923000, 'Shanghai': 34000000, 'Jakarta': 30000000, 'Seoul': 25514000, 'Guangzhou': 25000000, 'Beijing': 24900000, 'Karachi': 24300000, 'Shenzhen': 23300000, 'Delhi': 21753486}
하지만 dict_comprehension방식으로도 구현해보자.
풀이2 : dict_comprehension 사용
my_dict = {x[0] : x[1] for x in population_of_city}
print(my_dict)
iterable과 iterator를 비교하는 게 말장난 같아 보일 수도 있다.
하지만 이 두 개념은 "구분"돼야 하고, 그럴 수 있어야 한다.
요소(element) 3개를 갖는 리스트를 통해 iterable
의 개념을 알아보자.
"리스트는 iterable 한가?"
nums = [1, 2, 3]
for num in nums:
print(num)
매우 간단한 예제다.
경험적으로 리스트가 iterable하다는 걸 알 수 있다.
그렇다면 iterable하다는 걸 어떻게 알 수 있을까?
print(dir(nums))
결과
수많은 spercial variable들이 출력되지만 그 중 `__iter__
를 찾을 수 있다.정리 : <필요충분조건>
__iter__
<=>iterable하다
그렇다면,
nums.__iter__()
는 iterator인가?
nums = [1, 2, 3]
i_nums = nums.__iter__() # <-- i_nums = iter(nums)와 동일
print(dir(i_nums))
print(i_nums)
결과
__iter__
,__next__
<list_iterator object at 0x1064b9f70>
i_nums는 iterator임을 확인했다.
리스트가 iterator라는 말이 아니다.
nums라는 리스트가 있었고(iterable)
nums.__iter__()
또는 iter(nums)
가
iterator 가 되었다는 것이다.
이 미세한 변화가 왜 중요할까?
for문의 구현 방식이기 때문이다.
좀 더 나아가 보자
위에서 for 문을 통해 리스트의 iterable함을 예제로 확인했었다.
이번엔 for문을 사용하지 말고 동일하게 구현해보자.
nums = [1, 2, 3]
i_nums = iter(nums)
print(next(i_nums))
print(next(i_nums))
print(next(i_nums))
print(next(i_nums))
결과
1
2
3
Traceback (most recent call last):
File "/Users/khh180cm/generator.py", line 7, in
print(next(i_nums))
StopIteration
기존 리스트 nums는 __next__
없었는데 반해, i_nums에는 존재한다.
우리가 정의/선언한 리스트는 반복문에서 위와 같은 방식으로 작동되는 것이다.
아래와 같이 try/except을 추가하여 코드를 수정했다.
nums = [1, 2, 3]
# for문 작동원리
i_nums = nums.__iter__()
while True:
try:
print(next(i_nums))
except StopIteration:
break
Q. 아래의 for문을 기본 함수인 iter, next를 사용하여 while문으로 구현하시오.
D = {'a':1, 'b':2, 'c':3} for key in D.keys(): print(key)
풀이
D = {'a':1, 'b':2, 'c':3}
print(dir(D))
Run 결과, __iter__
메소드는 있으나, __next__
메소드는 없다.
따라서, 딕셔너리는 iterable하다. iterator는 아니다.
D = {'a':1, 'b':2, 'c':3}
i_D = iter(D)
print(i_D)
결과
: <dict_keyiterator
object at 0x1017ea540>
D = {'a':1, 'b':2, 'c':3}
i_D = iter(D)
while True:
try:
key = next(i_D)
value = D[key]
print(value)
except StopIteration:
break
결과
1
2
3
내장함수(built-in function) 중 하나인 range를
클래스로 구현해보자.
class MyRange:
def __init__(self, start, end):
self.value = start
self.end = end
# iterable한 MyRange 클래스
def __iter__(self):
return self
# MyRange클래스는 iterator다.
def __next__(self):
if self.value >= self.end:
raise StopIteration
current = self.value
self.value += 1
return current
# nums 객체
nums = MyRange(1, 10)
while True:
try:
print(next(nums))
except StopIteration:
break
위에서 iterable
과 iterator
개념을 배웠다.
generator는 __iter__
와 __next__
메소드가 없다.
그럼에도 불구하고, iterator처럼 구현이 된다.
개념 : 값이 실제로 쓰이기 전까지 연산을 미루는 방식
예시
def return_one():
print("return 1")
return 1
one_list = [return_one() for x in range(4)]
for one in one_list:
print(one)
결과
return 1
return 1
return 1
return 1
1
1
1
1
우리가 예상한 대로 결과가 나왔다. (list_comprehension)
one_list = [1, 1, 1, 1]
가 먼저 생성되고,
그 아래
for one in one_list:
print(one)
for문을 통해 숫자 1 이 연속해서 출력된다.
이번엔 아래 리스트 정의/선언 부분을
one_list = [return_one() for x in range(4)]
아래와 같이 바꿔보자.
one_list = (return_one() for x in range(4))
결과
return 1
1
return 1
1
return 1
1
return 1
1
이것이 Lazy evaluation이다.
맨 처음 예제와는 달리,
one_list가 미리 완성되지 않기 때문이다.
Lazy evaluation 방식은 함수로부터 return 1을 받으면
그때마다 아래의 for문이 구동된다.
for one in one_list:
print(one)
값이 실제로 사용되지 않을거면 연산을 시작하지 않기 때문에
시간과 메모리를 절약할 수 있다.
import time
L = [ 1,2,3]
def print_iter(iter):
for element in iter:
print(element)
def lazy_return(num):
print("sleep 1s")
time.sleep(1)
return num
print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ]
print_iter(comprehension_list)
print("generator_exp=")
generator_exp = ( lazy_return(i) for i in L )
print_iter(generator_exp)
차이점
- list_comprehension :
comprehension 리스트의 구현이 완료된다.
완료된 이후에 print_iter함수에 리스트가 인자로 전달된다.
즉, 프로세스가 독립적으로 작동된다.
comprehension 리스트 완성
-->print_iter함수 구동
- lazy evaluation(generator) :
generator_exp의 요소 하나가 생성되면 print_iter함수 구동
generator_exp의 마지막 요소 생성/접근하기 까지 상기 과정 반복됨.
즉, 필요한 시점에 연산을 진행하므로 메모리(자원)을 효율적으로 사용할 수 있다.
- One step at a time -