Python 더 잘 다루기

ROK·2022년 1월 4일
0

왜 파이썬인가

세상에 다양한 언어들이 존재하고 그 언어를 이용해 각국의 사람들과 소통할 수 있는 것처럼, 다양한 프로그램 언어들을 이용해 컴퓨터와 대화할 수 있다

C,C++,C#,JAVA,PHT,GO,JavaScript,Ruby,Perl 등 용도에 따라 수많은 언어가 있지만 그 중 파이썬을 공부하는 이유를 알아보자

퍼포먼스와 생산성
프로그래밍 언어에서 가장 중요하게 고려돼야 하는 것이 퍼포먼스와 생산성이다

퍼포먼스(성능)란 코드를 짜서 실행 시켰을 때 얼마나 빨리 처리가 되는 가를 말한다
퍼포먼스가 좋다고 한다면 특정한 연산을 빠르게 수행할 수 있다

언어별 특정 연산에 대한 수행 속도 결과

그림을 보면 C언어가 퍼포먼스 상위를 차지하고 파이썬은 상대적으로 많은 시간이 소요되는 것을 볼 수 있다

생산성은 똑같은 기능을 하는 프로그램을 얼마나 빨리 작성할 수 있는 가를 말한다
빠른 시간 안에 기능을 구현해야 할 때, 생산성은 중요하다

같은 기능을 구현한다고 해도 C 계열의 언어는 많은 시간과 코드가 필요하지만 파이썬의 경우는 상대적으로 적은 시간을 필요로 한다

퍼포먼스 vs 생산성
C와 파이썬을 비교하면 생산성이 높으면 퍼포먼스가 떨어지고, 퍼포먼스가 올라가면 생산성이 떨어지는 것을 알 수 있다
그렇다면 생산성과 퍼포먼스 중 어떤 것을 우선적으로 생각해야 할까?

정답은 목적과 상황에 맞게 언어를 선택한다
실무에서는 회사의 각 프로그램들에서 기존에 사용하고 있는 언어를 가장 먼저 고려하고, 그 다음으로 개발하고자 하는 프로젝트의 성능과 개발 기간을 고려해서 언어를 정한다.

파이썬을 배우면 좋은 이유

높은 생산성
파이썬은 다양한 모듈을 라이브러리화해서 제공하고 있고, pip를 이용해서 언제든지 쉽고 빠르게 설치할 수 있는 써드파티 라이브러리를 통해 개발 기간을 크게 단축시킬 수 있다
만약 어떤 기능을 구현하기 전에 검색을 해본다면 거의 대부분의 기능은 패키지로 만들어져있을 가능성이 높다

코드의 간결함
예시 자바와 파이썬의 코드 비교

# java
if(true) {
	System.out.println("apple"):
    if(true) {
    	System.out.println("banana"):
	}
}
# python
if True:
	print("apple")
    if Ture:
    	print("banana")

딱 한눈에 보기에도 파이썬이 간결한 것을 알 수 있다

빠른 개발 속도
코딩 속도 비교

같은 문제를 해결한다고 해도 C++의 경우 약 11시간 정도의 시간이 소요되지만 Python의 경우 약 3시간으로 4배나 빠르게 코딩을 할 수 있다

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

컴파일 언어

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

스크립트 언어

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

컴파일 언어와 스크립트 언어 참조

높은 생산성, 간결한 코드, 빠른 개발 속도, 스크립트 언어의 장점을 갖춘 파이썬


파이썬 잘 사용하기

for문

for문 효과적으로 사용하기
enumerate(), 이중 for문, list Comprehension, Generator 총 4가지 개념을 사용

for문 잘 쓰기 - enumerate()와 이중 for문

for문을 잘 쓰면 반복적으로 코드를 쓰지 않아 간결한 코드를 만드는데 도움이 된다.
my_list 값들 하나씩 출력하는 코드

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

for i in my_list:
	print("값: ", i)
    
출력
값 :  a
값 :  b
값 :  c
값 :  d

값이 쭉 출력되었다. 만약 100개 이상의 값을 출력한다고 했을 때 50번 째에 출력된 값을 물으면 대답하기 어려울 것이다.
이를 확인하는 방법은 enumerate()라는 기능을 사용한다. enumerate()는 리스트, 문자열, 튜플 등이 있는 경우 순서와 리스트의 값을 함께 반환해주는 기능이다.

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

for i, value in enumerate(my_list):를 이용하면 i에 번호가, value에 값이 나오게 된다. enumerate()를 통해 단순 for문이 아니라 순서에 대한 결과값도 함께 추가되었다.

이중 for문 사용하기

for안에 또 for를 사용한 것을 이중 for문이라고 한다. 더 나아가 삼중 사중 for문을 만들 수도 있지만 데이터가 많은 삼중 for문 이상일 때는 매우 느려지기 때문에 보통 이중 for문 까지만 사용한다.

이중 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,0,0,0,1,1,1,1의 순서로 나오고 j의 값은 a,b,c,d,a,b,c,d 순서로 나오는 것을 알 수 있다. i안에 j가 있기 때문에 j가 다 돌때까지 i0에서 고정되고 ja,b,c,d 순서로 나온다. j가 다 돌면 i는 다음으로 넘어간다.
이런 방식으로 [0,1], ['a','b','c','d'] 두 리스트를 조합해 만든 새로운 리스트(result_list)를 얻게 된다

리스트 컴프리헨션(list Comprehension)

파이썬이 제공하는 편리한 기능 중에 하나. 이는 리스트 등 순회형 컨테이너 객체로부터 이를 가공한 새로운 리스트를 생성하는 아주 간결하고 편리한 방법이다. 파이썬이 가진 가장 큰 매력 중에 하나로 컴프리헨션 기능은 리스트 뿐만 아니라 셋, 딕셔너리에 대해서도 적용 가능하다.

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

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

print(result_list)

출력
[(0, 'a'), (0, 'b'), (0, 'c'), (0, 'd'), (1, 'a'), (1, 'b'), (1, 'c'), (1, 'd')]

위에서 이중 for문으로 구현했던 내용과 동일한 결과값을 갖는 코드를 리스트 컴프리헨션을 이용해 한줄로 구현했다. 처음에는 어려울 수 있지만 계속 반복하다보면 적응이 된다.

제너레이터(Generator)

머신러닝을 하면 매우 많은 데이터를 다루게 된다. 데이터를 처리하는 반복 구조를 위해 for문을 필수적으로 사용해야 한다.
위의 코드를 반복해 my_list에 있는 데이터 셋을 하나씩 가져와서 공급해주는 제너레이터를 만든다. (my_list를 총 2번 반복해 8개의 데이터를 공급한다)

단순 for문을 사용해서 데이터를 공급하는 예시

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

위의 코드의 문제는 이중 for문이 다 돌아가는 걸 기다린 후, 반환된 result_list값에 또 for 문을 돌려야 한다. 만약 데이터가 수 천, 수 만개가 있다면 get_dataset_list(my_list)를 위해 엄청난 양의 데이터를 전부 메모리에 올려놔야 하는 문제점이 있다

Generator의 개념을 이용하여 데이터를 공급하는 코드

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...

yield 영어로 Yield는 '양보하다'라는 뜻을 갖고 있다. 파이썬에서 말 그대로 yield는 실행의 순서를 밖으로 양보한다. dataset_generator = get_dataset_generator(my_list)를 실행해도 generator object 만 반환할 뿐, 원하는 값을 반환하고 있지 않다. 실질적으로 데이터를 반환하는 것은 for 문에서 값을 하나씩 불러올 때다.

결과값을 보면 값을 한번 반환하고 '1 data loaded...'를 출력하는 것을 반복한다
이처럼 Generator가 없다면 우리는 엄청난 양의 리스트를 리턴 받아 메모리에 올려놓고 처리해야 하지만 Generator를 활용한다면 처리해야할 데이터를 1개씩 로드해서 사용할 수 있다.
이는 빅데이터를 처리해야할 머신러닝 상황에서 매우 용이하다.


Try-Except 예외 처리

프로그램을 개발하다 보면 필연적으로 에러의 늪에 빠지게 된다. 그 에러를 잡기 위한 수많은 노력들 중 하나가 바로 Try-Except이다. 이것을 예외 처리를 위한 방법이라고 한다.

예외(exceiption)이란 코드를 실행하는 중에 발생한 에러를 뜻한다. 즉 , 예외 처리는 코드를 수행하다가 예외(에러)가 발생했을 때 그 예외(에러)를 무시하게 하거나 예외(에러) 대신 적절한 처리를 해주게 하는 등의 작업을 의미한다. Try-Except의 작동 구조는 아래와 같다


그림에서 Try 이하의 Statements를 수행하고 만약 이 코드에 에러가 발생하면 Except안에 있는 코드가 바로 실행되고, 에러가 발생하지 않는다면 해당 코드는 정상적으로 작동하기 때문에 따로 추가적인 작업 없이 종료한다

Try-Except의 작동 예시

print(10/0)

출력
ZeroDivisionError: division by zero

에러가 발생하는 것을 볼 수 있다. 알다시피 0으로는 어떠한 값도 나눌 수 없기 때문에 에러가 발생하게 된다.
물론 처음부터 저러한 경우를 만들지 않으면 좋지만 실제 데이터 분석이나 프로그래밍을 하다 보면 이러한 경우가 자주 발생한다. 그럴때 예외 처리 구문을 통해 문제를 해결한다

에러가 발생했을 때 에러 메시지 대신 에러가 발생했다는 문구가 출력되도록 해본다.

a = 10
b = 0
try:
	#실행 코드
    print(a/b)

except:
	#에러가 발생했을 때 처리하는 코드
    print("에러 발생")

출력
에러 발생

에러 코드 없이 '에러 발생' 문구를 출력했다.
만약 에러가 없다면?

a = 10
b = 1
try:
	#실행 코드
    print(a/b)

except:
	#에러가 발생했을 때 처리하는 코드
    print("에러 발생")

출력
10.0

try 내부 코드가 정상적으로 동작했기 때문에 except 코드는 실행되지 않고 정상적인 결과값이 출력되었다.

더 나아가 except에서 에러가 발생했을 경우 알려주는 것이 끝이 아니라 에러가 발생하지 않을 값으로 바꿔서 결과를 출력하는 방법

a = 10
b = 0
try:
	#실행 코드
    print(a/b)

except:
	#에러가 발생했을 때 처리하는 코드
    print("에러 발생")
    b = b+1
    print("수정된 값 : ", a/b)

출력
에러 발생
수정된 값 : 10.0

except에서 잘못된 b의 값을 수정하고 수정된 값이 정상적으로 출력되는 것을 확인할 수 있다.


Multiprocessing

실행 시간 측정

내가 작성한 코드를 실행시킬 때 얼마나 시간이 소요되는지 궁금할 때 아래의 코드로 확인할 수 있다.

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

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

출력
time : 7.891654968261719e-05

간단한 코드이므로 아주 짧은 시간이 소요되는 것을 알 수 있다 (e-05)

Multiprocessing

멀티프로세싱은 컴퓨터가 작업을 처리하는 속도를 높여주는 방법 중 하나이다.
(단, 비약적으로 속도가 올라가거나 하지는 않는다)

위 사진을 보면 parallel processing, serial processing이 있다. parallel processing은 병렬 처리, serial processing은 순차 처리로 번역이 가능하다. 내가 지금까지 연습해온 코드는 순차 처리의 방식이다. 4개 중 한 개만 사용해왔는데 나머지 3개도 사용하는 방법을 알아보자

병렬 처리는 4개의 문자열이 동시에 처리되어 저장되고, 순차처리는 문자열이 하나씩 차례대로 처리되어 저장되는 것을 알 수 있다.

순차 처리 예제 -변수를 1억번 돌려보는 코드

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')
    
for num in num_list:
	count(num)
    
print("time :", time.time() - start)

출력
finish:p1

finish:p2

finish:p3

finish:p4

time : 9.392877340316772

병렬 처리 예제

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)

출력
finish:p3

finish:p1

finish:p2

finish:p4

time : 7.024041652679443

두 예제의 결과 값을 비교해 보면
순차 처리의 경우 리스트의 순서에 맞게 p1 p2 p3 p4의 순으로 출력되며 시간은 약 9.4초가 소모 되었고, 병렬 처리의 경우 결과 값의 순서가 리스트와 상관 없이 출력되며 시간은 약 7초가 소모되었다.

병렬 처리의 결과 값이 순서가 다른 이유는 코드가 거의 동시에 들어가 각자 처리되는 대로 결과값이 나오기 때문 (처리 시간은 각 코어의 점유 상황이나 여러 상황으로 달라진다)

병렬 처리 예제에 대한 설명

먼저 multiprocessing 모듈을 import하고 병렬 처리를 하고 싶은 함수(def count(name):)를 작성

import multiprocessing

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

이어서 if __name__== '__main__':이 나온다. 이는 코드 시작점을 여기로 하라는 명령어

num_list = ['p1','p2','p3','p4']

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

한 줄씩 설명

  • 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,000줄 가량 되는 코드를 작성했는데 똑같은 코드가 필요하다면 어떻게 할까?

물론 복사 붙여넣기를 하는 방법이 있지만 복붙을 10번만 한다고 해도 10,000줄짜리 코드가 작성된다 그렇다면 과연 가독성이 좋을까?? 전혀 아닙니다

또한 코드에 문제가 있어 수정하려고 한다면 10,000줄의 코드를 1,000줄씩 끊어 전부 수정하거나 삭제하고 다시 붙여넣기를 해야하는 일이 생길 수도 있습니다.

이 문제를 해결하기 위한 방법이 바로 함수이다.
함수는 아래의 형태로 만들 수 있다

def print_twice(text):
	print(text)
	print(text)
    
print_twice("Hello")

출력
Hello
Hello

일반 코드 예시를 먼저 본다

list_data = [10, 20, 30, 40]
list_data2 = [20, 30, 40, 50]

length = len(list_data)
max_result = list_data[0]
for i in range(length):
	if max_result < list_data[i]:
    		max_result = list_data[i]

print("최댓값 : ", max_result)

length = len(list_data2)
max_result = list_data2[0]
for i in range(length):
	if max_result < list_data2[i]
    		max_result = list_data2[i]

print("최댓값 : ", max_result)

출력
최댓값 : 40
최댓값 : 50

결과 값을 보면 알 수 있듯이 최댓값을 구하는 코드를 만들었다
위 코드를 보면 최댓값을 구하기 위해 같은 코드가 반복되는 것을 확인할 수 있다.
그럼 함수가 얼마나 효율적인지 확인해 본다.

list_data = [10, 20, 30, 40]
list_data2 = [20, 30, 40, 50]

def 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
    
print("최댓값 : ", max_function(list_data))
print("최댓값 : ", max_function(list_data2))

출력
최댓값 : 40
최댓값 : 50

아주 간결하게 함수를 사용하지 않은 코드와 동일한 결과값을 확인할 수 있다

이처럼 함수를 잘 만들면 유용한 점은

  • 코드의 효율성을 높여준다
  • 코드의 재사용성을 높여줘 개발에 시간이 적게 든다
  • 짧고 간결해 코드의 가독성도 좋아진다

함수 사용 팁

pass

파이썬에서 함수를 만들기 전에 함수 이름과 입력 정도만 먼저 만들어 놓는 경우도 있다.
이럴 때 함수 안을 비워둔 채로 이름만 작성한다면 에러가 난다

def empty_func():

출력
IndentationError: expected an indented block

이럴 때 사용하는 것이 pass다.

def empty_func():
	pass

출력

아무것도 출력되지 않고 종료되었다
pass는 기타 제어 흐름 도구로 하는일은 아무것도 하지 않는 것이다. 문법적으로 필요하지만 특별히 할 일이 없을 때 사용할 수 있다. pass는 함수 뿐만 아니라 if, while등 다양한 곳에 사용할 수 있다.

함수를 연속해 사용할 수 있다.

def say_something(txt):
	return txt

def send(function, count):
	for i in range(count):
		print(function)
        
send(say_something("Hello"), 2)

출력
Hello
Hello

say_something() 함수는 입력한 값을 반환해준다. 그래서 send(say_something("Hello"), 2)에서 say_something("Hello") 대신 그냥 "Hello"를 넣어도 동작하긴 하지만 함수를 연속해서 사용할 수 있다를 보여주기 위함이다.

함수안의 함수, 2개 이상의 return

코드를 작성하다 때로 함수를 이용해 return값을 여러개 받고 싶을 때가 있을 수도 있다. 예를 들어 숫자들이 담긴 list의 최댓값, 최솟값을 한번에 출력하고 싶을때의 예시를 본다.

리스트로 반환하기

list_data = [30, 20, ,35, 40]

def minmax_func(x_list):

	def inner_min_func(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_func(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)
    
	minmax_list = [x_min, x_max]
    
	return minmax_list
    
print("최솟값, 최댓값 : ", minmax_func(list_data))
print("최솟값 : ", minmax_func(list_data)[0])
print("최솟값 : ", minmax_func(list_data)[1])

출력
최솟값, 최댓값 :  [20, 40]
최솟값 :  20
최댓값 :  40

함수 안에 함수가 두 개나 들어있는 것을 볼 수 있다. 함수는 함수 안에 함수를 사용할 수 있지만 단, 함수 안에서 만든 함수는 함수 내부에서만 사용가능하다
예를 들어

print("최솟값, 최댓값 : ", inner_min_func(list_data))

를 실행한다면 에러가 발생한다.

또한 return을 이용해 두 개 이상의 값을 반환한다고 했는데

...
	x_min = inner_min_function(x_list)
	x_max = inner_max_function(x_list)
    
	minmax_list = [x_min, x_max]
    
	return minmax_list
...

위 코드를 보면 알 수 있듯이 최솟값, 최댓값의 결과를 x_min, x_max 변수에 넣고, 그 변수들을 minmax_list 리스트에 넣은 후 리스트를 return했다
return하는 것은 minmax_list 한 개이지만 그 안에 2개의 값을 넣어 뒀기 때문에 2개 이상의 값을 반환한다.

실제로 여러 개의 변수로 반환하는 방법도 있다
위 코드의 마지막에서

...
	x_min = inner_min_function(x_list)
	x_max = inner_max_function(x_list)
    
	return x_min, x_max

min_value, max_value = minmax_func(list_data)

리스트를 return하는 것이 아니라 ,(콤마)를 이용해 여러 개의 값을 반환하는 방법도 있다.

람다 표현식

람다(lambda)는 런타임에 생성해서 사용할 수 있는 익명 함수이다. 쉽게 말하면 이름 없는 함수
람다는 식 형태로 표현해 람다 표현식(lambda expression)이라 부른다.

더하기 함수

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

일반적으로 def를 사용해 함수를 작성하면 최소 2줄이 필요하다. 함수이름, 입력값, 함수 정의, 결과 반환 return 등을 작성해야 하기 때문에 하지만 람다를 사용한다면 한 줄로 작성 가능하다.

lambda x,y: x+y

위 예시를 순서대로 살펴보면

  • x,y는 입력 값을 의미한다. (x값과 y값이 입력으로 들어옴)
  • x+yreturn과 같다 앞의 add함수에 return x+y와 같이 람다에 :이후에 반환값이 나온다

이와 같이 람다를 def를 사용하지 않고 람다를 이용해 함수를 만들 수 있다.

람다 표현식을 사용하는 가장 중요한 이유는 함수의 인수 부분을 간단히 하기 위함이다

이와 같은 방식의 대표적인 예는 map()이다

map()함수는 입력받은 자료형의 각 요소가 함수에 의해 수행된 결과를 묶어서 map iterator 객체로 출력하는 역할을 한다

말이 어려우므로 예시를 본다

def list_mul(x):
	return x * 2
    
result = list(map(list_mul, [1,2,3]))
print(result)

# 각 함수를 실행할 때 마다 어떤 결과가 나오는지 확인하기 
# map_res = map(list_mul, [1,2,3])
# print(map_res)
# list_map_res = list(map_res)
# print(list_map_res)

출력
[2,4,6]

코드설명
list_mul() 함수는 숫자를 받은 뒤 두 배 값을 반환하는 함수
map()list_mul()함수와 리스트 [1, 2, 3]을 넣음
주의 map()의 결과는 그냥 map 객체이므로 결과값을 확인하기 위해 list() 사용 리스트 형태로 변환

map(f, iterable)은 입력으로 함수 f와 반복 가능한 iterable 객체(리스트, 튜플 등)을 받음
f = list_mul() 함수, iterable = [1,2,3] 리스트 입력
코드를 실행하면 리스트 안의 값들에 2를 곱한 결과가 나온다. 리스트 안의 운소들을 1,2,3 순서대로 list_mul() 함수에 차례로 넣고 출력을 받아 list형태로 바꿈

map()lambda 결합하기

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

출력
[2,4,6]

map() 이외에도 filter(), reduce() 등 람다 표현식과 자주 사용하는 함수들이 많이 있다 이는 위키독스:람다를 통해 확인

클래스(class)

클래스는 비슷한 역할을 하는 함수들의 집합, 객체를 표현하기 위한 문법이다.
예시 RPG 게임의 기사, 마법사, 궁수, 도적 등 의 직업을 클래스로 만들어 표현할 수 있다
각 클래스는 속성메서드를 가지는데 이는 기사의 체력, 마나, 공격력, 주문력 등과 같은 것이 속성이고, 스킬 베기, 찌르기 등을 메서드라고 한다.

클래스의 경우는 중요하고 양이 많기 때문에 따로 글을 작성할 계획이다.

모듈(Module)

모듈은 함수, 변수, 클래스를 모아 놓은 파일을 말한다. 즉 코드의 저장소라고 볼 수 있다.
모듈은 앞으로 파이썬을 사용한다면 자주 사용하게 될 중요한 기능 중 하나이다. 이미 만들어져 있는 모듈을 가져와 쓸 수도 있지만 직접 모듈을 만들어 사용할 수도 있다.

모듈 사용 예시
모듈을 만들어본다. 모듈 이름은 mycalculator.py이고 사칙연산을 수행하는 코드로 구성되어있다.

# mycalculator.py

test = "you can use this module."

def add(a,b):
	return a+b

def mul(a,b):
	return a*b

def sub(a,b):
	return a-b
   
def div(a,b):
	return a/b
    
class all_calc():

	def __init__(self, a, b):
		self.a = a
		self.b = b

	def add(self):
		return self.a + self.b
        
	def mul(self):
		return self.a * self.b

	def sub(self):
		return self.a - self.b

	def div(self):
		return self.a / self.b

mycalculator.py 모듈을 완성했다. 이제 완성한 모듈을 사용해본다.

먼저 import 모듈을 한다.

# import 모듈이름
import mycalculator

다음 mycalculator안의 함수를 사용하기 위해서는 아래와 같이 사용한다.

# 모듈이름.함수이름()
print(mycalculator.add(4,2))

출력
6

모듈이름이 mycalculator로 너무 길다면 as 구문을 이용해 모듈의 별명을 정할 수 있다

import mycalculator as mc

# 모듈이름.(함수이름()
print(mc.add(4,2))

출력
6

as를 이용해 mycalculatormc로 별명을 지정했다.

패러다임

패러다임(Paradigm)은 어떤 한 시대의 사람들의 견해나 사고를 근본적으로 규정하고 있는 테두리를 말하며, 인식의 체계 또는 사물에 대한 이론적인 틀이나 체계를 의미하는 개념이다.

프로그래밍에서 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해주고, 결정하는 역할을 한다. 프로그래밍 언어들은 각자 언어 마다 프로그래밍 패러다임을 갖고 있다. 하나의 패러다임을 가진 언어(Smalltalk, HASKELL)가 있고, 여러 개의 패러다임을 지원하는 언어(Python, Java, Lisp)가 있다.

데이터 사이언스를 공부하기 위해서 배울 것은 함수형 프로그래밍이다. 조금 어려운 내용이기 때문에 간단하게만 설명한다.

절차 지향 프로그래밍과 객체 지향 프로그래밍

절차 지향 프로그래밍은 일이 진행되는 순서대로 프로그래밍하는 방법이다

  • 장점 : 코드가 순차적으로 작성되어 있어 순서대로 읽기만 하면 이해가 가능하다
  • 단점 : 순차적으로 작성되어 있기 때문에 위에서 하나가 잘 못되면 아래는 연쇄적으로 문제가 생겨 유지, 보수가 어려운 단점이 있다. 일반적으로 코드 길이가 길어서 코드를 분석하기 어렵다

객체 지향 프로그래밍은 개발자가 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 한다. 객체를 먼저 작성하고 함수를 작성한다. 이렇게 작성된 객체는 객체간의 상호작용을 한다.

  • 장점 : 코드를 재사용하기 쉽다. 코드 분석이 쉽고 아키텍쳐를 바꾸기 쉽다.
  • 단점 : 객체 간의 상호작용이 있기 때문에 설계에서 많은 시간이 소요되며 설계를 잘 못하면 전체적으로 바꿔야 할 수도 있다.

우리가 배우는 파이썬은 객체 지향 프로그래밍 패러다임을 기본적으로 지원하고 있다.
객체 지향에 더 자세한 내용

함수형 프로그래밍

함수형 프로그래밍은 데이터 사이언티스트에게 적합한 프로그래밍 패러다임이다. 함수형 프로그래밍은 효율성, 버그 없는 코드, 병렬 프로그래밍과 같은 장점을 제공한다.

함수형 프로그래밍은 함수로 문제를 분해하고, 이 함수들은 입력을 받아 출력을 만들어내기만 한다. 주어진 입력이 함수를 거쳐 값이 출력되면 이 출력값은 함수 외부의 다른 변수나 함수에 의해 변하지 않는다.

함수형 프로그래밍이 데이터 사이언티스트에게 적합한 패러다임인 이유는 함수형 프로그래밍이 가지고 있는 특징 때문이다.

함수형 프로그래밍의 특징

순수성
함수형 프로그램에서 함수는 입력으로 동작을 시작해 출력을 만들어 낸다. 함수형 방식은 내부 상태를 수정하거나 함수의 반환값에서 보이지 않는 다른 변경사항들을 만드는 부작용이 있는 함수를 사용하지 않는다. 부작용이 전혀 없는 함수를 순수 함수라고 한다. 부작용을 피한다는 것은 프로그램이 실행될 때 해당 프로그램이 수정될 수 있는 상황을 엄격히 제한한다는 의미이며, 모든 함수의 출력은 입력에만 의존해야 한다.

순수성에 대한 예시

순수성이 없는 코드

A = 5

def impure_mul(b):
	return b * A
    
print(impure_mul(6))

출력
30

입력으로 들어오는 변수 외에 함수 밖의 변수 A도 함수 내에서 사용하기 때문에 순수성이 없다고 볼 수 있다.

순수성이 있는 함수

def pure_mul(a,b):
	return a * b
    
print(pure_mul(4,6))

출력
24

함수 내부에 함수 밖에서 가져오는 함수나 변수를 변경시키는 코드가 없이, 순수하게 함수 input 2개만을 이용해서 결과를 내보낸다.

모듈성
함수형 프로그래밍은 문제를 작은 조각으로 분해하도록 강제한다. 복잡한 변환을 한 함수 안에서 수행하는 거대한 함수보다. 한 가지 작업을 수행하는 작은 함수들로 쪼개어 만드는 것이 코딩하기에 더 쉽다. 작은 함수는 가독성도 좋고 오류를 확인하기도 더 쉽다. 결과적으로 프로그램은 더욱 모듈화가 된다.

디버깅과 테스트 용이성
함수형 프로그래밍으로 개발된 프로그램은 각각의 함수가 작고 명확하게 명시되기 때문에 디버깅을 쉽게 할 수 있다. 프로그램이 동작하지 않는다면, 각 함수는 올바른지 확인할 수 있는 포인트들이 된다. 각 함수의 입력과 출력을 확인하면서 예상되는 것과 다른 출력이 나오면 해당 부분이 문제기 때문에 디버깅이 쉽다.

각 함수는 잠재적으로 단위 테스트의 대상이기 때문에 테스트가 더 쉽다. 올바른 입력을 함수에 입력하고 결과가 예상과 일치하는지만 확인 하면 되기 때문에

참고 : 함수형 프로그래밍 HOWTO

파이써닉하게 코드 짜기

파이썬의 뛰어난 부분 중 하나는 가독성이다. 높은 수준의 가독성은 파이썬 언어 디자인의 핵심이라고도 할 수 있는데, 코드 작성은 한 번이지만, 코드를 다시 읽거나 고치는 일은 훨씬 많기 때문에 코드 가독성은 중요하다

파이썬의 가독성이 좋은 이유는 비교적 완벽한 코드 스타일 가이드라인과 파이써닉한 코드 작성법(이디엄)덕분이다.

파이썬은 pep8이라는 코드 스타일 가이드가 있다. 대부분의 개발자가 이 코드 스타일에 따라서 개발을 하고 있다. 개발자들이 따르는 스타일이 있다는 것은 많은 도움이 된다. 유지 보수 측면에서 빠른 코드 이해로 개발 기간이 줄고 가독성 측면에서도 도움이 된다

참고

규칙

공백

  • 한 줄의 코드 길이가 79자 이하여야 한다.
y = a + a + a + a # 79자 이하
  • 함수와 클래스는 다른 코드와 빈 줄 두개로 구분한다.
class a():
	pass
# 빈 줄
# 빈 줄
class b():
	pass
# 빈 줄
# 빈 줄
def c():
	pass
# 빈 줄
# 빈 줄
  • 클래스에서 함수는 빈 줄 하나로 구분한다.
class a():
	
	def b():
		pass
# 빈 줄
	def c():
		pass
  • 변수 할당 앞뒤에 스페이스를 하나만 사용한다.
y = 1
  • 리스트 인덱스, 함수 호출에는 스페이스를 사용하지 않는다
list = [1,2,3]
list[0] # 리스트 인덱스 호출

function(0) # 함수 호출
  • 쉼표(,), 콜론(:), 세미콜론(;) 앞에서는 스페이스를 사용하지 않는다
list = [1, 2, 3]: 
list[0:1]
if len(list) == 3: 
print list

주석

  • 코드의 내용과 일치하지 않는 주석은 피해야 한다.
  • 불필요한 주석은 피해야 한다.

이름

  • 변수명 앞에 _(밑줄)이 붙으면 함수 등의 내부에서만 사용되는 변수를 일컫는다
_list = []
  • 변수명 뒤에 _(밑줄)이 붙으면 라이브러리 혹은 파이썬 기본 키워드와의 충돌을 피하고 싶을때 사용한다.
import_ = "not_import"
- 소문자 L, 대문자 O, 대문자 I를 가능하면 사용하지 않는다. 특정 폰트에서 가독성이 굉장히 안좋다.
- 모듈명은 짧은 소문자로 구성되며, 필요하다면 밑줄로 나눈다.
```python
new_module.py
  • 클래스 명은 파스칼 케이스(PascalCase) 컨벤션으로 작성한다. 네이밍 컨벤션 아래에서 설명
class MyClass():
	pass
  • 함수명은 소문자로 구성하되 필요하면 밑줄로 나눈다
def new_fucntion():
	pass
  • 상수(Constant)는 모듈 단위에서만 정의하고 모든 단어는 대문자이며, 필요하다면 밑줄로 나눈다.
NEW_PI = 3.14 # 상수는 변하지 않는 변수

네이밍 컨벤션

실무에서는 다른 사람과 함께 코드를 짜야하는 경우가 많은데, 사람들마다 변수명을 적는 방식이 다르면 코드가 지저분해 보이고 가독성이 나빠진다. 가독성이 좋은 코드를 위해 통일성을 갖추는 코딩 스타일 가이드가 필요하다.

대표적인 네이밍 컨벤션은 snake_case, PascalCase, camelCase 세 가지 이다.

snake_case

  • 모든 공백을 "_"로 바꾸고 모든 단어는 소문자이다.
  • 파이썬에서 함수, 변수 등을 선언할 때 사용한다.

PascalCase

  • 모든 단어가 대문자로 시작한다.
  • UpperCamelCase, CapWords라고 불리기도 한다.
  • 파이썬에서 클래스를 선언할 때 사용한다.

camelCase

  • 처음은 소문자로 시작하고 이후 모든 단어의 첫 글자는 대문자로 한다.
  • lowerCamelCase라고 불리기도 한다.
  • 파이썬에서는 거의 사용하지 않음(Java에서 많이 사용)
profile
하루에 집중하자

0개의 댓글