티스토리 뷰

for문! 투더문

enumerate() / 이중 for문

enumerate는 열거한단 의미인데 리스트나 튜플 등에서 순서와 리스트의 값을 같이 반환해주는 기능이다.

my_list = ['a','b','c','d']

for i, value in enumerate(my_list):
    print("순번 : ", i, " , 값 : ", value)
순번 :  0  , 값 :  a
순번 :  1  , 값 :  b
순번 :  2  , 값 :  c
순번 :  3  , 값 :  d

i, value를 통해서 앞에서부터 순서대로 0번째 'a', 1번째 'b', ... 로 아주 편리하게 index를 줄 수 있다.

아 그리고 순번을 1부터 시작하고 싶을 땐 enumerate(my_list, start = 1) 이런 식으로 적으면 된다.

 

그럼 따블 for문을 한번 살펴보자. 

my_list = ['a','b','c','d']
result_list = []

for i in range(2):
    for j in my_list:
        result_list.append((i, j))
        
print(result_list)
[(0, 'a'), (0, 'b'), (0, 'c'), (0, 'd'), (1, 'a'), (1, 'b'), (1, 'c'), (1, 'd')]

의미를 따져보자면 i 는 우선 0~1까지 한번씩 뽑아야하고 j는 a,b,c,d를 차례로 뽑아내게 된다. 그럼 순서상 가장 안쪽 for문을 싹 돌리고 나와야하므로 (0, 'a'), (0, 'b'), ... 이렇게 앞이 0인 것들 부터 먼저 다 나오면 그다음 (1,'a')으로 시작해서 abcd까지 뽑아내게 됩니다. 그런데 이를 더 간단하게 표현할 수가 있다고 하는데...

my_list = ['a','b','c','d']

result_list = [(i, j) for i in range(2) for j in my_list]

print(result_list)

이렇게 해도 위와 같은 결과?! 무친...

이것이 list Comprehension인데 파이썬이 제공하는 편리한 기능 중 하나라고 한다. Set과 Dict에서도 적용 가능하다고 함!

 

참고 - range()

  • range(start, stop, step) : start에서 stop까지 step의 간격으로 라는 의미. stop은 미만으로 받기 때문에 포함시키지 않음. 예로 range(1, 10, 2) 이면 1부터 9까지 2씩 증가하는 [1, 3, 5, 7, 9] 가 나오게 된다. 하지만 실제로 반환하는 타입은 range이며, 리스트로 받아오고 싶다면 list(range(1, 10, 2))를 해야할 것!

Generator

: yield 를 사용하는 것이 특징이다. iterator를 생성해주는 function이라고 생각하면 되는데, 문제는 iterator는 뭔지 모르겠다. 우선은 간단하게만 적어보자면 iterator 는 next() 메소드를 이용해 데이터에 순차적으로 접근이 가능한 object 이다. 

그럼 다음 2개의 코드블럭을 살펴보면, 둘다 동일한 내용인데 아래 코드는 generator를 적용한 코드다. 

my_list = ['a','b','c','d']

def get_dataset_list(my_list):
    result_list = []
    for i in range(2):
        for j in my_list:
            result_list.append((i, j))
    print('>>  {} data loaded..'.format(len(result_list)))
    return result_list

for X, y in get_dataset_list(my_list):
    print(X, y)
>>  8 data loaded..
0 a
0 b
0 c
0 d
1 a
1 b
1 c
1 d
my_list = ['a','b','c','d']

# 인자로 받은 리스트로부터 데이터를 하나씩 가져오는 제너레이터를 리턴하는 함수
def get_dataset_generator(my_list):
    result_list = []
    for i in range(2):
        for j in my_list:
            yield (i, j)   # 이 줄이 이전의 append 코드를 대체했습니다
            print('>>  1 data loaded..')

dataset_generator = get_dataset_generator(my_list)
for X, y in dataset_generator:
    print(X, y)
0 a
>>  1 data loaded..
0 b
>>  1 data loaded..
0 c
>>  1 data loaded..
0 d
>>  1 data loaded..
1 a
>>  1 data loaded..
1 b
>>  1 data loaded..
1 c
>>  1 data loaded..
1 d
>>  1 data loaded..

차이가 확 느껴진다. 원하는 값을 바로 반환하는 것이 아니라 하나씩 처리해서 나갔다 들어오는 식이다.

자세하게 보면 이런 순서로 구문이 진행된다.

 

  • for 문이 실행되며, 먼저 generator 함수가 호출된다.
  • generator 함수는 일반 함수와 동일한 절차로 실행된다. 
  • 실행 중 중첩 for 문 안에서 yield 를 만나게 된다. 그러면 return 과 비슷하게 함수를 호출했던 구문으로 반환하게 된다. 여기서는 첫번재 i와 j 값인 0과 a 를 반환하게 된다. 하지만 반환 하였다고 generator 함수가 종료되는 것이 아니라 그대로 유지한 상태이다.
  • X, y 에는 yield 에서 전달 된 0과 a 값이 저장된 후 print 된다. 그 후 for 문에 의해 다시 generator 함수가 호출된다. 
  • 이때는 generator 함수가 처음부터 시작되는게 아니라 yield 이후 구문부터 시작되게 된다. 따라서 print('>>  1 data loaded..') 부분의 구문이 실행되고 위로 가서 j 값은 b로 이동한다.
  • 아직 for 문 내부이기 때문에 yield 구문을 만나 j 값이 b인 상태로 전달된다.( i 값은 여전히 0 )
  • X,y 는 0 b로 print 된다. (이후 반복)

그래서 이렇게 왜 하느냐고 궁금할텐데 유용한 점은 2가지!

1. 메모리를 효율적으로 사용 !

 

데이터의 크기가 커질수록, 예를 들어 길이가 1억짜리인 리스트를 받았을 때  generator를 사용하지 않은 위의 코드는 메모리에 1억 길이의 리스트를 올려 놓고 작업을 실행하게 되는 것과 같다. 다음 예시를 통해 어떤 차이가 나는지 보자.

>>> import sys

>>> sys.getsizeof( [i for i in xrange(100) if i % 2] )    # list
536

>>> sys.getsizeof( [i for i in xrange(1000) if i % 2] )
4280

>>> sys.getsizeof( (i for i in xrange(100) if i % 2) )    # generator
80

>>> sys.getsizeof( (i for i in xrange(1000) if i % 2) )
80

 

generator 의 경우 데이터 값을 한꺼번에 메모리에 적재 하는 것이 아니라 next() 메소드를 통해 차례로 값에 접근할 때마다 메모리에 적재하는 방식이라 list의 사이즈가 커지면 커질수록 효율성은 더욱 극대화 된다.

2. Lazy evaluation : 계산 결과 값이 필요할 때까지 계산을 늦추는 효과를 볼 수 있다.

수행 시간이 긴 연산에 대해 필요한 순간까지 그 속도를 늦출 수 있다는 장점이 있다고 하는데 이 부분도 아직은 잘 모르겠다. 



Try - Except 예외 처리하기

try-except 작동 구조

멋사-쏘카에서는 한번도 본 적 없는 문법이라 자주 쓰는지는 모르겠다. 하지만 코딩을 처음부터 차근차근 해나갈 때 분명히 유용하게 쓸 수도 있을 법하다.

a = 10
b = 0 

try:
    #실행 코드
    print(a/b)
		
except:
    print('에러가 발생했습니다.')
    #에러가 발생했을 때 처리하는 코드
    b = b+1
    print("값 수정 : ", a/b)

이런 느낌으로 쓰는건데 데이터 분석할 때 0으로 나눠진 데이터가 껴있어서 ZeroDivisionError: division by zero  이런 에러가 뜰 때가 굉장히 많다. 이런 부분들을 예외로 무시하고 위처럼 계속 실행되게 할 수 있다. 그리고 추후에 저런 작은 부분도 고치거나 결측값으로서 제외해버리면 아주 딱 좋아 !!!



실행 시간 측정!

내 코드의 성능을 측정하거나 epoch 돌리면서 사용하는 경우가 많다. 그래서 다음 블럭의 의미를 보면 a가 1~99까지 가는 for문을 돌렸을 때 걸린 시간을 측정하는 것이다.

import time
start = time.time()  # 시작 시간 저장

a = 1
for i in range(100):
	a += 1
 
# 작업 코드
print("time :", time.time() - start) # 결과는 '초' 단위 입니다.

Multiprocessing

https://sebastianraschka.com/Articles/2014_multiprocessing.html

import multiprocessing
import time

num_list = ['p1','p2', 'p3', 'p4']
start = time.time()

def count(name):
    for i in range(0, 100000000):
        a = 1+2
    print("finish:"+name+"\n")
    

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 4)
    pool.map(count, num_list)
    pool.close()
    pool.join()

print("time :", time.time() - start)

멀티프로세싱하지 않은 경우보다 2배 가까이 빠르게 결과가 출력된다. 이 병렬처리 방식에 대해서 좀 자세히 얘기해보면 count 함수를 만든 후에 if __name__ == '__main__':  이 나온다. 이것은 코드 시작점을 여기로 하라는 명령어다.

그리고 한 줄씩 자세히 설명하자면,

  • pool = multiprocessing.Pool(processes = 4) : 병렬 처리 시, 4개의 프로세스를 사용하도록 합니다. CPU 코어의 개수만큼 입력해 주면 최대의 효과를 낸다.
  • pool.map(count, num_list) : 병렬화를 시키는 함수로, count 함수에 num_list의 원소들을 하나씩 넣어 놓는다. 여기서 num_list의 원소는 4개이므로 4개의 count 함수에 각각 하나씩 원소가 들어가게 된다.
    즉, count('p1'), count('p2'), count('p3'), count('p4')가 만들어짐.
  • pool.close() : 일반적으로 병렬화 부분이 끝나면 나오는데, 더 이상 pool을 통해서 새로운 작업을 추가하지 않을 때 사용.
  • pool.join() : 프로세스가 종료될 때까지 대기하도록 지시하는 구문으로써 병렬처리 작업이 끝날 때까지 기다리도록 하는 녀석.


코드를 간결하게 😎

1. 함수를 사용하자

  • 코드의 효율성을 높여줄 뿐만 아니라
  • 코드의 재사용성을 높여줘 개발하는 시간이 적게 걸리게 되고
  • 뭘 하고자 하는지 누구나 알기 쉬워 코드의 가독성도 좋아진다.

2. 함수 속 함수 - 여러개의 변수로 반환하자.

list_data = [30, 20, 30, 40]

def minmax_function(x_list):
        
    def inner_min_function(x):
        length = len(x)
        min_result = x[0]
        for i in range(length):
            if min_result > x[i]:
                min_result = x[i]
        return min_result
    
    def inner_max_function(x):
        length = len(x)
        max_result = x[0]
        for i in range(length):
            if max_result < x[i]:
                max_result = x[i]
        return max_result
        
    x_min = inner_min_function(x_list)
    x_max = inner_max_function(x_list)
    
    return x_min, x_max

min_value, max_value = minmax_function(list_data)

print("최솟값은 : ", min_value)
print("최댓값은 : ", max_value)
최솟값은 :  20
최댓값은 :  40

3. lambda

여러 코드에서 보면서 무슨 뜻인지 전혀 모르겠단 생각을 많이 했던 람다 표현식. 오늘 좀 제대로 이해해보고 가자.

def add(x, y):
    return x + y 
이 코드를 람다를 사용해서 한줄로 표현해보자.

print( (lambda x,y: x + y)(10, 20) )
30
  • 먼저 x, y 는 입력값을 의미합니다. 즉, x값과 y값이 입력으로 들어온다는 의미.
  • 두 번째, x + y는 return 부분과 같습니다. add 함수에도 return x + y가 있었던 것과 같이 lambda 에도 ':' 이후에 반환값으로 나옴.
  • 마지막은 (10, 20)은 각각 앞에 있던 x, y 입력값. 만약 입력이 x, y, z 이라면 (10, 20, 30) 이렇게 세 개의 값을 넣게 될 것이고, 보통 함수 안의 함수를 간단히 만들 때, def를 이용해 만들지 않고 람다를 이용해서 함수를 만들게 된다.

 

def list_mul(x):
     return x * 2

result = list(map(list_mul, [1, 2, 3]))
print(result)
[2, 4, 6]

lambda를 다시 써볼건데 여기에 map()까지 곁들인다면?! map도 중요한 녀석인데, map의 구조는

map(f, iterable) 함수 f 와 반복 가능한 iterable 객체(리스트, 튜플 등) 가 들어가야 한다.

result = list(map(lambda i: i * 2 , [1, 2, 3]))
print(result)
[2, 4, 6]

반복 가능한 리스트 [1, 2, 3]이 lambda로 들어가면서 위에서 자세하게 공부한 것 처럼 i * 2에 1, 2, 3이 들어가서 리턴해주고 앞 쪽의 i 로 입력 받는다. 그래서 [2, 4, 6]이 출력되는 것.


클래스 / 모듈 / 패키지 (글이 너무 길어져서 나중에 따로 다뤄야할 듯...)

프로그래밍 패러다임

: 절차지향? 객체지향? 함수형 프로그래밍

하 cv...아 함수형!!! 길은 쓸데없이 긴게 머리는 ㅈㄴ 나빠서

이 부분도 아주 좋은 블로그의 링크를 아래에 걸겠다. 죄송함다... 힘드네요.

 

 

 

References and Citations

ㅍㅍㅋㄷ님 블로그는 볼게 많다.

 

python generator(제너레이터) 란 무엇인가

Python Generator  먼저 python docs 의 generator 에 대한 정의를 보자. generator A function which returns an iterator. It looks like a normal function except that it contains yield statements for pr..

bluese05.tistory.com

 

프로그래밍 패러다임에 대하여.

 

알아두면 좋은 파이썬 개념 - 1. 프로그래밍 패러다임

파이썬 절차지향, 객체지향, 함수형 프로그래밍

jinwoo1990.github.io

 

'Python' 카테고리의 다른 글

Python 기초정리(문자열, 파일 다루기)  (0) 2021.12.29
Python 기초 정리(제어문&자료형)  (0) 2021.12.28
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함