day 6 파이썬 배우기 2
파이썬 배우기 2
파이썬 응용
먼저 퍼포먼스(성능) 란 어떤 언어든 코드를 짜서 실행을 시켰을 때 얼마나 빨리 처리가 되는가를 말합니다.
생산성이란 한 마디로 똑같은 기능을 하는 프로그램을 얼마나 빨리 작성할 수 있는가 입니다.
그래서 어떤 언어를 써야 돼? 목적에 맞게, 상황에 맞게 언어를 선택하고 사용하는 방법을 배우면 됩니다. 실무에서는 먼저 회사의 각 프로그램들에서 기존에 사용하고 있는 언어를 가장 먼저 고려하고, 그다음에는 개발하고자 하는 프로젝트의 성능과 개발 기간을 고려해서 언어를 정하게 됩니다.
파이썬은 높은 생산성을 위해서 사용을 합니다.
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')]
for문 잘 써보기 - enumerate()와 이중 for문
my_list = ['a','b','c','d']
for i in my_list:
print("값 : ", i)
..
..
값 : a
값 : b
값 : c
값 : d
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')]
참고 - range()
range(start, stop, step) : range(2)와 같이 정수가 들어가야 하며, 0부터 시작해서 2개인 list ([0, 1]) 를 만듭니다. 즉, stop 까지 포함하지는 않습니다. range(1, 10, 2) 이라면 1부터 9까지 2씩 증가하는 리스트 ([1, 3, 5, 7, 9]) 를 만듭니다.
리스트 컴프리헨션(list Comprehension)
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문으로 구현했던 내용과 완전히 동일한 기능을 리스트 컴프리헨션을 이용하여 1줄로 구현해 보았습니다
제너레이터(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)
get_dataset_list(my_list) 를 위해 엄청난 양의 데이터를 전부 메모리에 올려놔야 한다는 것입니
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)
제너레이터를 활용할 때는 1억 개의 데이터를 전부 메모리에 올려놓을 필요가 없이 현재 처리해야 할 데이터를 1개씩 로드해서 사용할 수 있게 됩니다. 이것은 빅데이터를 처리해야 할 머신러닝 상황에서 매우 요긴합니다.
Try - Except 예외 처리하기
a = 10
b = 0
try:
#실행 코드
print(a/b)
except:
print('에러가 발생했습니다.')
#에러가 발생했을 때 처리하는 코드
b = b+1
print("값 수정 : ", a/b)
..
..
에러가 발생했습니다.
값 수정 : 10.0
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)
for num in num_list:
count(num)
print("time :", time.time() - start)
..
..
finish : p1
finish : p2
finish : p3
finish : p4
time : 9.236518383026123
병렬 처리를 이용한 예제
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)
if __name__ == '__main__':
pool = multiprocessing.Pool(processes = 4)
pool.map(count, num_list)
pool.close()
pool.join()
print("time :", time.time() - start)
..
..
finish : finish : p1p4
finish :
p3
finish : p2
time : 2.4471380710601807
그럼 병렬 처리를 사용하는 방법에 대해 좀 더 자세히 알아보겠습니다. 일단, multiprocessing모듈을 import합니다. 그리고 병렬 처리를 하고 싶은 함수를 작성합니다. 그 아래에 count(name) 함수를 만들어보겠습니다.
import multiprocessing
def count(name):
for i in range(0, 100000000):
a = 1 + 2
print("finish : ",name)
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)
if __name__ == '__main__':
pool = multiprocessing.Pool(processes = 4)
pool.map(count, num_list)
pool.close()
pool.join()
print("time :", time.time() - start)
..
..
코드 실행
finish : p2finish :
finish : p3
p4
finish : p1
-
pool = multiprocessing.Pool(processes = 4) : 병렬 처리 시, 4개의 프로세스를 사용하도록 합니다.
-
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() : 프로세스가 종료될 때까지 대기하도록 지시하는 구문으로써 병렬처리 작업이 끝날 때까지 기다리도록 합니다.
함수
- 코드의 효율성을 높여줄 뿐만 아니라
- 코드의 재사용성을 높여줘 개발하는 시간이 적게 걸리게 되고
- 내가 뭘 하고자 하는지 모두가 알기 쉬워 코드의 가독성도 좋아집니다.
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
함수 안의 함수 & 2개 이상의 return
- 리스트로 반환하기
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)
minmax_list = [x_min, x_max]
return minmax_list
print("최솟값, 최댓값은 : ", minmax_function(list_data))
print("최솟값은 : ", minmax_function(list_data)[0])
print("최댓값은 : ", minmax_function(list_data)[1])
..
..
최솟값, 최댓값은 : [20, 40]
최솟값은 : 20
최댓값은 : 40
- 여러 변수로 반환하기
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) 람다 표현식
map() 이외에도 filter(), reduce() 등 람다 표현식과 자주 쓰이는 함수가 많이 있습니다.
result = list(map(lambda i: i * 2 , [1, 2, 3]))
print(result)
..
..
[2, 4, 6]
(4) 클래스, 모듈, 패키지
-
클래스(Class): 클래스는 비슷한 역할을 하는 함수들의 집합이라고 볼 수 있습니다
-
모듈(Module): 모듈은 함수, 변수, 클래스를 모아 놓은 파일을 말합니다. 즉, 코드의 저장소라고 볼 수 있습니다.
모듈
# 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
# 아래 코드를 이용하여 현재 클라우드 저장소 위치에 모듈을 저장해 줍니다.
code = '# mycalculator.py\ntest = "you can use this module."\n\ndef add(a, b):\n return a + b\n \ndef mul(a, b):\n return a * b\n\ndef sub(a, b):\n return a - b\n\ndef div(a, b):\n return a / b\n\n\nclass all_calc():\n\n def __init__(self, a, b):\n self.a = a\n self.b = b\n\n def add(self):\n return self.a + self.b\n \n def mul(self):\n return self.a * self.b\n\n def sub(self):\n return self.a - self.b\n\n def div(self):\n return self.a / self.b'
f = open("mycalculator.py", "w")
f.write(code)
f.close()
# import 모듈이름
import mycalculator
# 모듈이름.함수이름()
print(mycalculator.add(4, 2))
..
..
6
패키지
# pip install 패키지이름
$ pip install pandas
프로그래밍 패러다임과 함수형 프로그래밍
로그래밍 언어들은 각자 언어 저마다의 프로그래밍 패러다임을 갖고 있습니다. 어떤 언어(Smalltalk, HASKELL)들은 하나의 패러다임만을 지원하기도 하고 또 어떤 언어(Python, Lisp, Java)들은 여러 개의 패러다임을 지원하기도 합니다.
우리는 데이터 사이언스를 공부하는 입장이기 때문에 여기에 적합한 패러다임을 여러분에게 소개하려고 합니다. 바로 함수형 프로그래밍입니다.
절차 지향 프로그래밍
일이 진행되는 순서대로 프로그래밍하는 방법입니다.
-
장점 : 코드가 순차적으로 작성되어 있어 순서대로 읽기만 하면 이해가 가능합니다.
-
단점 : 순차적으로 작성되어 있기 때문에 위에서 하나가 잘못되면 아래도 연쇄적으로 문제가 생겨서 유지보수가 어렵습니다. 일반적으로 코드 길이가 길어서 코드를 분석하기 어렵습니다.
객체 지향 프로그래밍
객체지향 프로그래밍은 개발자가 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 합니다. 객체를 먼저 작성하고 함수를 작성합니다. 이렇게 작성된 객체는 객체 간의 상호작용이 있습니다.
-
장점 : 코드를 재사용하기 쉽습니다. 코드 분석이 쉬우며 아키텍처를 바꾸기 쉽습니다.
-
단점 : 객체 간의 상호작용이 있기 때문에 설계에서 많은 시간이 소요되며 설계를 잘못하면 전체적으로 바꿔야 할 수도 있습니다.
파이썬은 객체지향 프로그래밍(OOP, Object Oriented Programming) 패러다임을 기본적으로 지원하고 있습니다.
함수형 프로그래밍
함수형 프로그래밍은 데이터 사이언티스트에게 적합한 프로그래밍 패러다임입니다. 함수형 프로그래밍은 효율성, 버그 없는 코드, 병렬 프로그래밍과 같은 장점을 제공합니다.
함수형 프로그래밍은 함수로 문제를 분해합니다. 이 함수들은 입력을 받아서 출력을 만들어 내기만 하며, 주어진 입력이 함수를 통과하고 값이 생성되면 이 출력값은 함수 외부의 다른 변수나 함수에 의해 변하지 않습니다.
함수형 프로그래밍이 데이터 사이언티스트에게 적합한 프로그래밍 패러다임이라고 말하는 이유는, 함수형 프로그래밍이 기본적으로 가지고 있는 특징 때문입니다. 아래 특징들을 확인하기 전에 한 가지 예를 들어보겠습니다. 함수형 프로그래밍은 병렬 프로그래밍 측면에서 장점이 있다고 말한 것을 기억하실 것입니다.
AI 연구가 갑자기 붐처럼 일어나게 된 이유가 무엇 때문이었는지 기억하시나요? 알고리즘의 고도화, 알파고 등 이유는 많지만 GPU를 이용한 병렬 처리가 굉장히 큰 역할을 했습니다. 병렬 처리는 어려운 프로그래밍 방법의 하나로 유명합니다. 기본적으로 병렬 처리를 코딩하기도 어려울 뿐더러 제약 조건 또한 많기 때문입니다. 제약 조건 중에 하나만 얘기하자면 하나의 데이터에 동시에 여러 함수가 접근한다고 가정을 했을 때 이들 함수 중에 하나라도 기존 데이터를 수정하는 함수가 있으면 기존 데이터로 출력을 예상할 수가 없게 됩니다. 만약, 반대로 순차 처리를 한다면 여러 개의 함수가 순서대로 처리되기 때문에 기존 데이터를 바꾼 함수 이후로의 데이터 출력은 예측이 가능한 것이죠.
함수형 프로그래밍의 특징
- 순수성
순수성에 대한 코드 예시를 보겠습니다. 아래는 순수성이 없는 코드입니다.
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
- 모듈성
함수형 프로그래밍은 문제를 작은 조각으로 분해하도록 강제합니다. 복잡한 변환을 한 함수 안에서 수행하는 거대한 함수보다, 한 가지 작업을 수행하는 작은 함수들로 쪼개어 만드는 것이 코딩하기에 더 쉽습니다. 작은 함수는 가독성도 좋고 오류를 확인하기도 더 쉽습니다. 결과적으로 프로그램은 더욱 모듈화가 됩니다.
- 디버깅과 테스트 용이성
함수형 프로그래밍으로 개발된 프로그램은 각각의 함수가 작고 명확하게 명시되기 때문에 디버깅을 쉽게 할 수 있습니다. 프로그램이 동작하지 않는다면, 각 함수는 데이터가 올바른지 확인할 수 있는 포인트들이 됩니다. 각 함수의 입력과 출력을 확인하면서 예상되는 것과 다른 출력이 나오면 해당 부분이 문제이기 때문에 디버깅이 쉽습니다.
각 함수는 잠재적으로 단위 테스트의 대상이기 때문에 테스트가 더 쉽습니다. 올바른 입력을 함수에 입력하고 결과가 예상과 일치하는지 확인만 하면 되기 때문이죠.
파이써닉 코드
- 참고 자료 코드 스타일
Whitespace
한 줄의 코드 길이가 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
리스트 인덱스, 함수 호출에는 스페이스를 사용하지 않습니다.
my_list = [1, 2, 3]
my_list[0] # 리스트 인덱스 호출
my_function(0) # 함수 호출
쉼표(,), 쌍점(:), 쌍반점(;) 앞에서는 스페이스를 사용하지 않습니다.
my_list[1, 2, 3]; my_list[0:1]
if len(my_list) == 3: print my_list
주석
- 코드의 내용과 일치하지 않는 주석은 피해야 합니다.
- 불필요한 주석은 피해야 합니다.
이름 규칙
변수명 앞에 _(밑줄)이 붙으면 함수 등의 내부에서만 사용되는 변수를 일컫습니다.
_my_list = []
변수명 뒤에 _(밑줄)이 붙으면 라이브러리 혹은 파이썬 기본 키워드와의 충돌을 피하고 싶을 때 사용합니다.
import_ = "not_import"
소문자 L, 대문자 O, 대문자 I를 가능하면 사용하지 마세요. 특정 폰트에서는 가독성이 굉장히 안 좋습니다.
모듈(Module) 명은 짧은 소문자로 구성되며, 필요하다면 밑줄로 나눕니다.
my_module.py
클래스 명은 파스칼 케이스(PascalCase) 컨벤션으로 작성합니다. 네이밍 컨벤션은 뒤에서 다시 설명하겠습니다.
class MyClass():
pass
함수명은 소문자로 구성하되 필요하면 밑줄로 나눕니다.
def my_function():
pass
상수(Constant)는 모듈 단위에서만 정의하며 모두 대문자에 필요하다면 밑줄로 나눕니다.
MY_PI = 3.14 # 상수는 변하지 않는 변수입니다.
- 네이밍 컨벤션(Naming convention) 실무에선 다른 사람들과 같이 코드를 짜야 하는 경우가 많습니다. 사람들마다 변수명을 적는 방식이 다르면 코드가 깔끔해보이지 않기 때문에 가독성이 안 좋습니다. 가독성이 좋은 코드를 짜는 방법은 통일성을 갖는 것입니다. 통일성을 갖기 위해선 사람들이 공유하는 코딩 스타일 가이드가 필요합니다. 그래서 파이썬에서는 pep8 이라는 코딩 스타일 가이드를 가지고 있습니다. pep8 코딩 스타일 가이드는 위에서 소개했기 때문에 이번엔 이름을 작성할 때 사용하는 네이밍 컨벤션에 대해서 알아보겠습니다.
대표적인 네이밍 컨벤션은 snake_case, PascalCase, camelCase 입니다. 각 네이밍 컨벤션의 기준에 맞춰 코드를 작성하시면 가독성이 높은 코드가 됩니다.
- snake_case
모든 공백을 “_“로 바꾸고 모든 단어는 소문자입니다. 파이썬에서는 함수, 변수 등을 명명할 때 사용합니다. ex) this_snake_case
- PascalCase
모든 단어가 대문자로 시작합니다. UpperCamelCase, CapWords라고 불리기도 합니다. 파이썬에서는 클래스를 명명할 때 사용합니다. ex) ThisPascalCase
- camelCase
처음은 소문자로 시작하고 이후의 모든 단어의 첫 글자는 대문자로 합니다. lowerCamelCase라고 불리기도 합니다. 파이썬에서는 거의 사용하지 않습니다(java 등에서 사용) ex) thisCamelCase
Leave a comment