[wecode 9일차] python 추가 과제

hyuckhoon.ko·2020년 6월 2일
0

What I learned in wecode

목록 보기
41/109

1. list_comprehension

과제 1)

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하였다.



과제 2)

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)



2. iterator

1) iterable 과 iterator

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

2) 과제

Q. 아래의 for문을 기본 함수인 iter, next를 사용하여 while문으로 구현하시오.

    D = {'a':1, 'b':2, 'c':3}
    for key in D.keys():
        print(key)

풀이

1) 딕셔너리는 iterable한가? 그리고 iterator인가?

    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


3. (참고) range 클래스 구현하기

내장함수(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



3. generator

위에서 iterableiterator개념을 배웠다.
generator는 __iter____next__메소드가 없다.
그럼에도 불구하고, iterator처럼 구현이 된다.

과제 1) lazy evaluation이란?

개념 : 값이 실제로 쓰이기 전까지 연산을 미루는 방식

예시

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)

과제 2) lazy evaluation 장점

값이 실제로 사용되지 않을거면 연산을 시작하지 않기 때문에
시간과 메모리를 절약할 수 있다.

반면, 메로리 절약이라는 기존의 장점을 단점으로 만들 수도 있다.

길이가 매우 길고, 크기도 큰 리스트가 있다고 하자.

최종 출력이 되는 시점을 생각해보면, 리스트의 계산을 한번에 수행해야 하기 때문에 메모리 오버헤드가 이슈가 발생할 수도 있다.



과제 3) list_comprehension과의 차이점

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)

차이점

  1. list_comprehension :
    comprehension 리스트의 구현이 완료된다.
    완료된 이후에 print_iter함수에 리스트가 인자로 전달된다.
    즉, 프로세스가 독립적으로 작동된다.
    comprehension 리스트 완성 --> print_iter함수 구동


  2. lazy evaluation(generator) :
    generator_exp의 요소 하나가 생성되면 print_iter함수 구동
    generator_exp의 마지막 요소 생성/접근하기 까지 상기 과정 반복됨.
    즉, 필요한 시점에 연산을 진행하므로 메모리(자원)을 효율적으로 사용할 수 있다.




                                     - One step at a time - 

0개의 댓글