파이썬에서 함수와 파일을 활용하는 법에 대해서 알아보자.
함수는 파이썬에서 코드를 재사용하고 조직화하기 위한 수단이다.
일반적으로, 같은 Code Block이 한 번 이상 실행될 것이라고 예상되면 해당 코드를 함수로 작성하는 것이 더 낫다.
함수는 def 예약어로 정의하고, return 예약어를 사용해서 값을 반환할 수 있다.
이때, return 문은 몇 개가 되든 상관 없으며 return 이 없다면 함수는 none을 반환한다. 즉, 함수에서 return이 이루어지는 횟수는 한 번이지만, case 별로 다른 return문을 작성할 수 있다는 뜻이다.
def my_function(x, y, z=1.5): # 함수는 일반 인자와 키워드 인자를 받을 수 있다.
if z > 1:
return z * (x + y)
else:
return z / (x + y)
함수는 '전역'과 '지역' 두 가지 스코프(영역)에서 변수를 참조한다. 변수의 '스코프'를 설명하는 다른 용어로 '네임 스페이스'가 있다. 함수 내에서 선언된 변수는 '기본적으로' 지역 스코프에 속한다.
지역 스코프는 함수가 호출될 때 생성되며 함수의 인자를 통해 즉시 생성된다. 함수의 실행이 끝나면 사라진다.
def func():
new_a = []
for i in range(5):
new_a.append(i)
func() # 함수호출
new_a
#NameError
하지만 다음의 경우, 변수는 지역 네임스페이스가 아닌 전역으로 생성되기 때문에 데이터가 사라지지 않는 것을 확인할 수 있다.
new_a = []
def func():
for i in range(5):
new_a.append(i)
func()
new_a
#[0, 1, 2, 3, 4]
함수 내부에서 global 예약어를 사용하여 전역 네임스페이스로 생성 가능하지만, 전역 변수는 사용에 주의해야 한다.
a = None
def bind_a_variable():
global a
a = []
bind_a_variable()
print(a)
#[]
또한, 파이썬은 함수의 return문에 여러 개의 반환값을 설정할 수 있는데, 이때 반환값들은 인터프리터가 하나의 튜플로 묶어서 반환된다.
def multiple_return():
a=1
b=1
c=1
d=1
e=1
return a, b, c, d, e # 여러 개의 값을 튜플 형태로 반환
result = multiple_return()
print(result) # 출력: (1, 2, 3, 4, 5)
함수의 반환값을 받는 할당 변수를 그 개수에 맞게 설정한다면, 인터프리터가 자동으로 튜플의 element들을 split하여 각 변수에 할당해준다.
def example_function():
result1 = 1
result2 = 2
return result1, result2 # 2개의 반환값
# 함수 호출 후 반환값을 받는 변수(할당 변수)
a, b = example_function() # 수신 변수 (Receiving Variables)
print(a) # a는 첫 번째 리턴값 (1)을 받음
print(b) # b는 두 번째 리턴값 (2)을 받음
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()
print(a,b,c)
#5,6,7
return_value=f()
print(return_value)
#(5,6,7)
파이썬에서는 함수도 객체이므로 다른 언어에서는 힘든 객체 생성 표현을 쉽게 할 수 있다.
데이터를 정제하기 위해 다음과 같은 문자열 리스트를 변형해야 한다고 가정하자
states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda',
'south carolina##', 'West virginia?']
공백을 제거하거나 필요 없는 문장부호를 제거하는 등의 작업이 필요하다.
이는 내장 문자열 메서드와 정규 표현식을 위한 re 표준 라이브러릴 이용해서 해결할 수 있다.
import re
def celan_strings(strings):
result=[]
for value in strings:
value = value.strip() # 문자열의 양쪽 끝 띄어쓰기 삭제
value = re.sub('[!#?]','',value) # 특수기호 제거
value = value.title() # 문자열 첫 글자를 대문자로 치환
result.append(value)
return result
clean_string(states)
#['Alabama','Georgia','Georgia','Georgia','Florida','South Carolina','West #Virginia']
다른 유용한 접근법으로는 적용할 함수를 리스트에 담아두고 각각의 문자열에 적용하는 것이다.
def remove_func(value): #특수기호를 없애줄 수 있는 함수 생성
return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_func, str.title]
#전처리에 필요한 함수들의 List 생성
def clean_strings(strings,ops):
result=[]
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
clean_strings(states,clean_ops)
이와 같이 함수가 객체인 것을 이용하여 더 실용적인 패턴을 통해 처리할 수 있다.
파이썬은 익명 함수(람다 함수)는 값을 반환하는 단순한 한 문장으로 이루어진 함수를 지원한다. lamda 예약어로 정의할 수 있다.
람다 함수는 데이터 분석에서 특히 편리하다. 데이터를 변형하는 함수의 인자가 함수를 받아야 하는 경우가 매우 많기 때문이다.
람다 함수를 사용하면 함수의 구성이 복잡하지 않은 경우, 실제 함수를 대입하는 것보다 더 간결해진다.
def apply_to_list(some_list,f)
return [f(x) for x in some_list]
ints=[4,0,1,5,6]
apply_to_list(ints,lambda x:x*2)
#out: [8,0,2,10,12]
이 경우 retrun 값은 ints라는 list에 함수 f(x)가 적용되어 각 elements에 2가 곱해진 값이 들어가게 된다.
return에 쓰인 리스트 컴프리헨션은 따로 정리해두었다.
다른 예제로, 다음 문자열 리스트를 각 문자열에서 다양한 문자가 포함된 순서로 정렬된다고 가정하자.
strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
strings.sort(key=lambda x: len(set(x)))
#'유일한 값'만 원소로 취할 수 있는 집합(set)을 이용
strings
['aaaa', 'foo', 'abab', 'bar', 'card']
lambda의 사용법은 단시간에 이해하기 힘들 수 있다. 이와 관련하여 따로 글을 작성해놓았으니, 참고하면 좋다.
커링(Curring)은 함수에서 일부 인자만 취하는 새로운 함수를 만드는 기법이다.
예를들어, 2개의 숫자를 더하는 함수가 있다고 가정하자
def add_num(x,y):
return x+y
이 함수를 이용해서 하나의 변수만 인자로 받아 5를 더해주는 add_five 함수를 생성할 수 있다.
add_five=lambda y: add_num(5,y)
파이썬은 '리스트 내의 객체' 나 '파일의 각 로우' 같은 순차적인 자료를 순회하는 방법을 제공한다. '이터레이터 프로토콜'을 이용해 순회 가능한 객체를 만들 수 있다. 예를들어, 사전을 순회하면 사전 키가 반환된다.
some_dict = {'a': 1, 'b': 2, 'c': 3}
for key in some_dict:
print(key)
#a
#b
#c
이터레이터는 for문 같은 컨텍스트에서 사용될 경우 객체를 반환한다. 리스트나 리스트와 유사한 객체를 취하는 대부분의 메서드는 순회 가능한 객체도 허용된다.
이터레이터: next() 함수 호출 시 계속 그다음 값을 반환하는 객체
제너레이터: 이러한 이터레이터를 생성해주는 함수
dict_iterator =iter(some_dict)
dict_iterator
#<dict_keyiterator at 0x7f9da46f97b0>
list(dict_iterator)
#['a', 'b', 'c']
제너레이터를 생성하려면 함수에서 return을 사용하는 대신 yield 예약어를 사용한다.
def squares(n=10):
print('Generating squares from 1 to {0}'.format(n ** 2))
for i in range(1, n + 1):
yield i ** 2
제너레이터를 호출하더라도 코드가 즉각적으로 실행되지 않는다. 제너레이터로부터 값을 요청하면 그때서야 제너레이터의 코드가 실행된다.
이렇게 생성된 이터레이터 객체는 순회가 끝나고 난 후, 재사용이 불가능하다. 그러므로 사용하고자 할 때마다 제너레이터를 이용하여 이터레이터를 생성해주어야 한다.
gen = squares()
gen
#<generator object squares at 0x7f9da4e9a9d0>
for x in gen:
print(x, end=' ')
#Generating squares from 1 to 100
#1 4 9 16 25 36 49 64 81 100
제너레이터를 생성하는 더 간단한 방법은 제너레이터 표현식을 사용하는 것이다.
gen=(x**2 for x in range(100))
gen
제너레이터 표현식은 리스트 표현식을 인자로 받는 파이썬 함수에서도 사용할 수 있다.
sum(x**2 for x in range(100)
#328350
dict(i,i**2 for i in range(5)
#{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
견고한 프로그램을 작성하려면 파이썬의 오류와 예외를 잘 처리해야 한다. 예를들어, 파이썬의 float 함수는 문자열을 부동소수점수로 변환할 수 있지만, 적절하지 않는 입력에 대해서는 ValueError와 함께 실패한다.
float("1.2345")
#1.2345
float('something')
#ValueError
적절하지 않은 입력에 대해서는 입력을 그대로 반환하는 개선된 float 함수를 작성해보자. 이렇게 하려면
try/except 블록을 사용해서 float 함수를 호출하면 된다.
def attempt_float(x):
try:
return float(x) #처음엔 try 블록을 시도해보고
except:
return(x) #except 블록에 있는 코드는 try 블록에서 예외가 발생했을 때 실행된다.
float 함수가 valueerror가 아닌 예외를 발생시키는 경우도 있다
float((1, 2))
#TypeError
TypeError는 정당한 오류이므로 그대로 두고 ValueError만 무시하고 싶다면 except 뒤에 처리할 예외의 종류를 적어주면 된다.
def attempt_float(x):
try:
return float(x)
except ValueError:
return x
튜플을 사용해서 여러 개의 예외를 한 번에 처리할 수도 있다.
def attempt_float(x):
try:
return float(x)
except (TypeError,ValueError):
return x
try 블록의 성공 여부와 관계없이 실행시키고 싶은 코드는 finally 블록에, try 블록이 성공했을 경우에만 실행시키고 싶은 코드는 else 블록에 표현하면 된다.
f = open(path, 'w')
try:
write_to_file(f)
except:
print('Failed')
else:
print('Succeeded')
finally:
f.close()
이 책에서는 대부분 pandas.read_csv 같은 고수준의 도구를 사용해서 디스크로부터 파일을 읽어와 파이썬 자료구조에 저장한다. 하지만 파이썬에서 파일을 어떻게 다루는지 이해하는 것도 중요하다.
파일을 읽고 쓰기 위해 열 때는 내장 함수인 open을 이용해서 파일의 '상대 경로'나 '절대 경로'를 넘겨주어야한다.
path = 'example.txt' #example.txt 에 텍스트 더미를 넣어놓아야함.
f=open(path)
기본적으로 파일은 읽기 전용 모드인 'r'로 열린다. 파일 핸들러 f를 리스트로 생각할 수 있으며 파일의 매 줄을 순회할 수 있다.
for line in f:
print(line)
#Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consequat justo #ac ultricies commodo. Quisque pharetra mauris at consectetur fringilla. Sed et #nunc in lacus condimentum pharetra. Proin vestibulum est eget pulvinar #elementum. Sed lobortis eros sed purus lacinia, vitae finibus tortor vestibulum. #Integer vitae mauris diam. Nullam in est sit amet est maximus tristique. Nam eu #ipsum id enim hendrerit efficitur. Donec et semper dolor, sit amet fringilla ex. #Maecenas vel tincidunt sem. Cras vitae magna vitae leo interdum suscipit. Sed at #condimentum urna, ac varius tortor. Vivamus volutpat tristique enim, sit amet f
f.close()
파일을 읽을 때는 read, seek, tell 메서드를 주로 사용하게 되는데 read 메서드는 해당 파일에서 특정 개수만큼의 문자를 반환한다. 또한, 읽은 바이트만큼 파일 핸들의 위치를 옮긴다. tell 매서드는 현재 위치를 알려준다.
f2 = open(path, 'r') # Binary mode
f2.read(10)
#'Lorem ipsu'
f2.tell()
#10
seek 메서드는 파일 핸들의 위치를 해당 파일에서 지정한 바이트 위치로 옮긴다.
f.seek(3)
f.tell()
#3
이제 기본적인 내용은 모두 숙지했다. 다음 챕터에서는 Numpy를 알아보고 파이썬에서 배열 연산을 하는 방법을 함께 배워보자