파이썬 기초 (2)

68034 단어 파이썬파이썬

파이썬 성능과 한계

언어에서 가장 중요하게 고려되는 부분은 퍼포먼스생산성이다.

1) 퍼포먼스

퍼포먼스(성능)은 어떤 언어든 코드를 짜서 실행을 시켰을 때 얼마나 빨리 처리가 되는가를 말한다.

즉, 퍼포먼스가 좋은 언어는 연산이 빠르고, 퍼포먼스가 안 좋은 언어는 연산이 느리게 수행된다.

아래는 언어별 특정 연산 수행 속도 결과이다.

2) 생산성

생산성은 시간과 관련이 있다. '똑같은 기능을 하는 프로그램을 얼마나 빨리 작성할 수 있는가' 라고 할 수 있다.

3) 퍼포먼스 VS. 생산성

생산성이 올라가면 퍼포먼스가 떨어지고 퍼포먼스가 올라가면 생산성이 떨어진다. 즉, 퍼포먼스와 생산성은 trade-off가 상당한 걸 확인할 수 있다.

어떤 언어를 사용해야 할까?

목적과 상황에 맞게 언어를 선택하면 된다.

실무에서는 먼저 회사의 각 프로그램들에서 기존에 사용하고 있는 언어를 가장 먼저 고려하고, 그다음에는 개발하고자 하는 프로젝트의 성능과 개발 기간을 고려해서 언어를 정하게 된다.

결국 성능과 시간 모두 잘 고려해야한다.


파이썬을 배워야하는 이유

1) 높은 생산성

[출처 : xkcd.com/353/]

위 그림은 파이썬 터미널에서 import antigravity를 실행하면 나오는 이스터에그 이미지이다. 놀라울 만큼 다양한 모듈을 이미 라이브러리화해서 제공하고 있다. 뿐만 아니라 pip를 통해 얼마든지 쉽게 설치할 수 있는 써드파티 라이브러리를 통해 개발 기간을 크게 단축시킬 수 있다.

2) 코드의 간결함
java

if(true) {
    System.out.println("첫 번째");
    if(true) {
        System.out.println("두 번째");
    }
}
if True :
    print("첫 번째")
    if True :
        print("두 번째")

3) 빠른 개발 속도

위의 표를 살펴보자. 같은 문제를 C++로 짜게 되면 평균 11시간 정도의 시간이 걸리고 파이썬으로 짜게 되면 약 3시간의 시간이 걸리는 걸 확인할 수 있다. 따라서 성능과 상관없이 개발이 빨리 돼야 하는 프로젝트를 하게 된다면 파이썬을 사용하는게 좋다.

4) 스크립트 언어(인터프리터 언어)
스크립트 언어는 인터프리터 언어라고 불리기도 한다. Python과 같은 스크립트 언어는 컴파일 언어에는 없는 강력한 장점을 가지고 있기 때문에 유용하다.

컴파일 언어

  • 실행 전 소스 코드를 컴파일하여 기계어로 변환 후 해당 파일을 실행
  • 이미 기계어로 변환된 것을 실행하므로 비교적 빠름
  • 컴파일 시점에 소스 코드의 오류를 잡기 쉬움
  • 같은 소스 코드도 다른 환경(PC, mobile 등)에서 실행하려면 다시 컴파일(기계어로 변환) 해야함

스크립트 언어(인터프리터 언어)

  • 코드를 작성함과 동시에 인터프리터가 기계어로 번역하고 실행함
  • 코드 번역 과정이 있어 비교적 느림
  • 주 사용 목적이 뚜렷하게 개발되어 사용하기 쉬운 편
  • 명령줄로 코드를 즉시 실행할 수 있음

더 자세한 설명은 아래 링크 참조

Interpreter (computing)


for문 활용하기

1) enumerate()
순서와 리스트의 값을 함께 반환해 주는 enumerate()를 사용해보자!

1 my_list = ['a','b','c','d']
2
3 for i, value in enumerate(my_list):
4    print("순번 : ", i, " , 값 : ", value)
/----------------------------------------/
# 출력
순번 :  0  ,:  a
순번 :  1  ,:  b
순번 :  2  ,:  c
순번 :  3  ,:  d

for i, value in enumerate(my_list)를 이용하면 i에 순번이, value에 해당 순번의 데이터 값이 나오게 됩니다. 이를 언패킹이라고 한다.

2) 리스트 컴프리헨션(list Comprehension)
리스트 등 순회형 컨테이너 객체로부터 이를 가공한 새로운 리스트를 생성하는 아주 간결하고 편리한 방법이다. 셋(Set), 딕셔너리(Dict)에 대해서도 적용 가능하다.

1 my_list = ['a','b','c','d']
2
3 result_list = [(i, j) for i in range(2) for j in my_list]
4
5 print(result_list)
/----------------------------------------------------------/
# 출력
[(0, 'a'), (0, 'b'), (0, 'c'), (0, 'd'), (1, 'a'), (1, 'b'), (1, 'c'), (1, 'd')]

3) 제너레이터(Generator)
my_list에 있는 데이터셋을 하나씩 가져와서 공급해 주는 제너레이터를 만드는 코드이다.

1 my_list = ['a','b','c','d']
2
3 # 인자로 받은 리스트를 가공해서 만든 데이터셋 리스트를 리턴하는 함수
4 def get_dataset_list(my_list):
5    result_list = []
6    for i in range(2):
7        for j in my_list:
8            result_list.append((i, j))
9    print('>>  {} data loaded..'.format(len(result_list)))
10    return result_list
11
12 for X, y in get_dataset_list(my_list):
13    print(X, y)
/----------------------------------------------------------/
# 출력
>>  8 data loaded..
0 a
0 b
0 c
0 d
1 a
1 b
1 c
1 d

첫 번째 코드의 문제는 이중 for 문이 다 돌아가는 걸 기다린 후, 반환된 result_list 값에 대해 또 for 문을 돌려야 한다는 것이다.

1 my_list = ['a','b','c','d']
2
3 # 인자로 받은 리스트로부터 데이터를 하나씩 가져오는 제너레이터를 리턴하는 함수
4 def get_dataset_generator(my_list):
5    result_list = []
6    for i in range(2):
7        for j in my_list:
8            yield (i, j)   # 이 줄이 이전의 append 코드를 대체
9            print('>>  1 data loaded..')
10
11 dataset_generator = get_dataset_generator(my_list)
12 for X, y in dataset_generator:
13    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..

영어로 "Yield"라는 단어는 "양보하다"라는 뜻을 갖고 있죠. 파이썬에서도 마찬가지로 yield는 코드 실행의 순서를 밖으로 "양보"한다.


예외 처리

프로그램을 개발하다 보면 필연적으로 에러에 늪에 빠지게 된다. 예외 처리는 코드를 수행하다가 예외(에러)가 발생했을 때 그 예외(에러)를 무시하게 하거나 예외(에러) 대신 적절한 처리를 해주게 하는 등의 작업을 의미한다. 이런 에러를 잡기위해서는 Try - Except를 사용한다. Try - Except의 구조는 아래와 같다.

오류 발생

1 print(10/0)
/-----------/
# 출력
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
/tmp/ipykernel_16/179726827.py in <module>
----> 1 print(10/0)

ZeroDivisionError: division by zero

Try - Except 처리

  • 에러 발생
1 a = 10
2 b = 0
3 try:
4    #실행 코드
5    print(a/b)
6		
7 except:
8    #에러가 발생했을 때 처리하는 코드
9    print('에러가 발생했습니다.')
/-------------------------------/
# 출력
에러가 발생했습니다.
  • 에러가 없을 때
1 a = 10
2 b = 1
3 try:
4    #실행 코드
5    print(a/b)
6		
7 except:
8    #에러가 발생했을 때 처리하는 코드
9    print('에러가 발생했습니다.')
/-------------------------------/
# 출력
10.0
  • 에러 발생 후 수정
1 a = 10
2 b = 1
3 try:
4    #실행 코드
5    print(a/b)
6		
7 except:
8     print('에러가 발생했습니다.')
9     #에러가 발생했을 때 처리하는 코드
10    b = b+1
11    print("값 수정 : ", a/b)
/-------------------------------/
# 출력
에러가 발생했습니다.
값 수정 :  10.0

Multiprocessing

Multiprocessing에 앞서 실행 시간 측정 방법에 대해 알아보자!

1 import time
2 start = time.time()  # 시작 시간 저장
3
4 a = 1
5 for i in range(100):
6	a += 1
7 
8 # 작업 코드
9 print("time :", time.time() - start) # 결과는 '초' 단위
/-----------------------------------------------------/
# 출력
time : 7.581710815429688e-05

위의 코드와 같이 time모듈의 time()함수를 사용한다.

Multiprocessing(멀티프로세싱)은 컴퓨터가 작업을 처리하는 속도를 높여주는 방법 중 하나이다. 그렇다고 멀티프로세싱을 이용하면 갑자기 자전거가 자동차의 속도로 빨리 가게 되지는 않는다. 비유하자면 하나의 자전거를 이용해 여러 명이 한 명씩 순차적으로 목적지까지 가다가, 여러 자전거를 이용해서 여러 명이 동시에 목적지까지 가게 되는 것과 같다.

즉, 병렬처리를 하는 것이다.

[출처 : https://sebastianraschka.com/Articles/2014_multiprocessing.html]

순차처리

1 import time
2
3 num_list = ['p1','p2', 'p3', 'p4']
4 start = time.time()
5
6 def count(name):
7    for i in range(0, 100000000):
8        a = 1 + 2
9        
10    print("finish:"+name+"\n")
11
12 for num in num_list:
13    count(num)
14
15 print("time :", time.time() - start)
/--------------------------------------/
# 출력
finish:p1

finish:p2

finish:p3

finish:p4

time : 9.080145835876465

병렬처리

1 import multiprocessing
2 import time
3
4 num_list = ['p1','p2', 'p3', 'p4']
5 start = time.time()
6
7 def count(name):
8    for i in range(0, 100000000):
9        a = 1+2
10    print("finish:"+name+"\n")
11    
12 if __name__ == '__main__':
13    pool = multiprocessing.Pool(processes = 4)
14    pool.map(count, num_list)
15    pool.close()
16    pool.join()
17
18 print("time :", time.time() - start)
/----------------------------------------------/
# 출력
finish:p4

finish:p1

finish:p2

finish:p3

time : 7.054991960525513

순차처리와 병렬처리를 비교해보면 병렬처리가 속도가 더 빠른 것을 확인할 수 있다. 또 병렬처리는 순차처리와 달리 완료 순서가 다른 것을 확인할 수 있다. 이유는 각 프로세스에 작업 코드가 거의 동시에 들어가서 각자 처리 후 결과가 나오기 때문이다. 각 코어의 점유 상황이나 여러 이유로 인해 시간차가 생긴다.


함수 사용하기

프로그래밍을 하다 보면 같은 코드를 여러 번 중복해서 입력하는 케이스가 있다. 짧은 코드의 경우에는 크게 문제가 없을 수 있겠지만, 1000줄짜리 코드같이 긴 코드인 경우에는 10번만 복사해도 10000줄이 된다. 이럴 때 사용하는 것이 바로 함수(Function) 이다.

1 def function_f(input_x):
2	output_x = input_x*input_x
3	return output_x

아래는 리스트 안의 요소 중 최대값을 찾는 코드이다.

  • 함수 미사용
1 list_data = [10, 20, 30, 40]
2 list_data2 = [20, 30, 40, 50]
3
4 length = len(list_data)
5 max_result = list_data[0]
6 for i in range(length):
7    if max_result < list_data[i]:
8        max_result = list_data[i]
9        
10 print("최댓값은 ", max_result)
11
12 length = len(list_data2)
13 max_result = list_data2[0]
14 for i in range(length):
15    if max_result < list_data2[i]:
16        max_result = list_data2[i]
17        
18 print("최댓값은 ", max_result)
/----------------------------------/
# 출력
최댓값은  40
최댓값은  50
  • 함수 사용
1 list_data = [10, 20, 30, 40]
2 list_data2 = [20, 30, 40, 50]
3
4 def max_function(x):
5    length = len(x)
6    max_result = x[0]
7    for i in range(length):
8        if max_result < x[i]:
9            max_result = x[i]
10    return max_result
11
12 print("최댓값은 ", max_function(list_data))
13 print("최댓값은 ", max_function(list_data2))
/--------------------------------------------/
# 출력
최댓값은  40
최댓값은  50

위의 코드를 보면 함수를 사용할 때와 사용하지 않을 때의 코드가 차이나는 것을 확인할 수 있다. 이렇듯 함수를 사용하면

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

람다 표현식

람다 표현식은 함수 이름이 없는 함수라고 할 수 있다. 람다 표현식은 식 형태로 되어 있어 람다 표현식(lambda expression)이라 부른다.

함수 사용

1 def add(x, y):
2    return x + y

람다 사용

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

아래 링크를 이용하면 람다에 대해 자세히 확인할 수 있다.

위키독스: 람다


클래스, 모듈, 패키지

클래스

클래스는 비슷한 역할을 하는 함수들의 집합이라고 볼 수 있다.

모듈

모듈은 함수, 변수, 클래스를 모아 놓은 파일을 말한다. 코드의 저장소라고도 볼 수 있다.

패키지

패키지(라이브러리)는 여러 모듈을 하나로 모아둔 폴더라고 할 수 있다.

패키지 설치는 pip명령어를 통해 설치한다.

# pip install 패키지이름
$ pip install pandas

좋은 웹페이지 즐겨찾기