파이썬 내장 자료 구조, 함수 및 파일

이 장에서는 앞으로 자주 사용될 파이썬 내장 자료형 튜플, 리스트, 사전, 집합에 대해서 알아본다. 또한 자신만의 함수를 만들고 사용하는 방법에 대해서도 다룬다. 계산된 결과들을 저장 장치에 저장하는 방법에 대해서도 살펴본다.

데이터 구조와 열거형

파이썬 자료 구조는 간단하고 강력하다. 이러한 자료 구조를 잘 활용할 수 있도록 익히는 것은 유능한 파이썬 프로그래머가 갖추어야 할 조건이다.

튜플(tuple)

튜플은 불변이고 길이가 고정되어 있다. 튜플을 만드는 간단한 방법은 소괄호와 쉼표를 사용하는 것이다.

튜 = (1, 2, 3)

또는 소괄호를 사용하지 않고 사용해도 된다.

In [2]:
 = 1, 2, 3

Out[2]:
(1, 2, 3)

tuple(<반복 가능 객체>) 함수를 이용해서 튜플을 만들 수 있다. 대표적인 반복 가능 객체로는 문자열, 리스트, 튜플 등이 있다.

In [3]:
 = tuple("안녕하세요")

Out[3]:
('안', '녕', '하', '세', '요')

튜플의 성분에 접근할 때는 모든 열거형에서 사용할 수 있는 대괄호 안에 인덱스를 사용한다.

In [4]:
[1]
Out[4]:
'녕'

튜플은 불변이기 때문에 성분을 바꿀 수 없다.

In [9]:
 = tuple(["안녕", [1, 2], False])
[2] = True
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-42a601bf1f94> in <module>()
      1= tuple(["안녕", [1, 2], False])
----> 2 [2] = True

TypeError: 'tuple' object does not support item assignment

하지만 튜플 성분이 가변 객체이면 가변 객체의 성분은 변경할 수 있다.

In [10]:
[1].append('금')

Out[10]:
('안녕', [1, 2, '금'], False)

튜플들은 더하기 연산자를 이용하여 결합하여 새로운 튜플을 만들 수 있다.

In [11]:
(1, 2) + ('가', '나')
Out[11]:
(1, 2, '가', '나')

튜플에 정수 곱하기를 해서 정수배만큼 반복해서 만들어 낼 수 있다.

In [12]:
('안', '녕', '?') * 3
Out[12]:
('안', '녕', '?', '안', '녕', '?', '안', '녕', '?')

튜플 풀기(unpacking)

할당문 왼쪽에 오른쪽에 있는 튜플의 성분의 갯수에 해당하는 변수 튜플을 할당할 수 있다.

In [13]:
,  = (1, 2)
print(, )
1 2

가장 자주 사용되는 풀기 용법은 다음과 같이 반복문에서이다.

In [17]:
 = [('강개리', 12, '남'), ('유진', 20, '여'), ('김호성', 21, '남')]

for 이름, 나이, 성별 in :
    print("이름: {} \t나이: {} \t성별: {}".format(이름, 나이, 성별))
이름: 강개리         나이: 12  성별: 남
이름: 유진  나이: 20  성별: 여
이름: 김호성         나이: 21  성별: 남

튜플 메소드

튜플은 불변형이라서 메소드는 index, count 2개 있다. 이 메소드는 열거형 공통 메소드이다. index(성분)성분이 튜플에서 처음으로 나오는 인덱스를 반환한다.

In [18]:
 = (1, 2, 1, 2, 3)
.index(1)
Out[18]:
0

count(성분)성분이 튜플에 몇 번 있는지를 알려준다.

In [19]:
.count(2)
Out[19]:
2

리스트(list)

리스트는 대괄호와 쉼표를 이용해서 만들거나 list(<반복 가능 객체>) 함수를 이용해서 만들 수 있다.

In [21]:
 = [1, (2, 3), 4]
 = list(range(5))

Out[21]:
[0, 1, 2, 3, 4]

리스트는 튜플과 다르게 가변 객체이다. 즉, 리스트 안의 성분을 변경할 수 있다.

In [22]:
[1] = "하나"

Out[22]:
[1, '하나', 4]

성분 추가 및 삭제

append(성분) 메소드를 이용해서 리스트의 끝에 성분을 추가할 수 있다.

In [23]:
 = [1, 2, 3]
.append('넷')

Out[23]:
[1, 2, 3, '넷']

insert(삽입위치, 성분) 메소드를 이용해서 삽입위치성분을 삽입할 수 있다.

In [24]:
 = [1, 2, 3]
.insert(0, '영')

Out[24]:
['영', 1, 2, 3]

insert() 메소드는 성분을 원하는 위치에 삽입하기 위해서 나머지 성분들을 하나씩 옮겨야 하기 때문에 append() 메소드에 비해서 비효율적이다. pop(인덱스) 메소드는 인덱스 위치에 있는 성분을 리스트에서 제거한다.

In [26]:
 = [1, 2, 3]
.pop(2)

Out[26]:
[1, 2]

성분 객체를 건네줌으로 성분을 제거할 수 있다. 같은 것이 여러개 있으면 앞에 것부터 제거한다.

In [27]:
 = ['하', '나', '하', '나', '둘']
.remove('하')

Out[27]:
['나', '하', '나', '둘']

성분이 리스트 안에 포함되어 있는지를 innot in를 이용해서 알 수 있다.

In [28]:
 = ['하나', 2, '셋']
'셋' in 
Out[28]:
True

성분이 포함되어 있는지를 판단할 때는 setdict를 이용하는 것이 효율적이다. setdict는 해시 테이블을 이용해서 성분을 찾기 때문에 훨씬더 효율적이다.

리스트 합치기

+ 연산을 이용하면 두 리스트 객체를 합쳐서 새로운 객체를 만들 수 있다.

In [29]:
 = [1, 2]
 = [3, 4]
 + 
Out[29]:
[1, 2, 3, 4]

기존의 리스트에 다른 리스트를 합칠 때는 extend 메소드를 사용하는 것이 효율적이다.

In [35]:
리리 = [[1, 2], [3, 4]]
 = []
for 항목 in 리리:
    .extend(항목)

Out[35]:
[1, 2, 3, 4]

정렬

sort() 메소드를 이용해서 리스트 객체의 순서를 내부적(in-place)으로 바꿀 수 있다. 내부적으로 변경한다는 것은 새로운 객체를 만들지 않고 기존의 객체의 순서를 변경한다는 뜻이다.

In [37]:
 = [9, -3, 0, 20]
.sort()

Out[37]:
[-3, 0, 9, 20]

sort(key=func) 인자로 key에 정렬 조건함수를 넘겨줌으로 원하는 방식으로 정렬할 수 있다. 다음과 같이 len 함수를 건네줌으로 문자열의 길이로 정렬을 할 수 있다.

In [38]:
 = ['아름다운', '첫', '사랑', '기억', '끝']
.sort(key=len)

Out[38]:
['첫', '끝', '사랑', '기억', '아름다운']

부분선택(slicing)

리스트와 같은 열거형은 대괄호 안에 시작인덱스:끝인덱스 형식을 이용하여 부분을 선택할 수 있다.

In [39]:
 = [1, 2, 3, 4, 5]
[1:4]
Out[39]:
[2, 3, 4]

주의해야 할 것은 끝인덱스 항목은 포함되지 않는다는 것이다. 부분선택을 통해서 값을 할당할 수도 있다.

In [40]:
 = [1, 2, 3, 4, 5]
[1:3] = ['ㅁ', 'ㅠ']

Out[40]:
[1, 'ㅁ', 'ㅠ', 4, 5]

시작인덱스, 끝인덱스를 생략할 수 있다. 시작인덱스가 생략되면 처음부터를 의미하고 끝인덱스가 생략되면 끝까지를 의미한다.

In [41]:
 = [1, 2, 3, 4, 5, 6, 7]
[:4]
Out[41]:
[1, 2, 3, 4]
In [42]:
[4:]
Out[42]:
[5, 6, 7]

부분선택을 할 때 간격을 추가하여 선택할 수 있다.

In [44]:
 = [1, 2, 3, 4, 5, 6, 7]
[1::2]
Out[44]:
[2, 4, 6]

인덱스로 음수를 사용할 수 있다. 음수는 거꾸로 순서를 매긴다.

내장 열거 함수(built-in sequence function)

enumerate 함수

열거형을 다루다보면 성분에 해당하는 인덱스가 필요한 경우가 종종 발생한다. enumerate(<반복 가능 객체>)는 (인덱스, 성분) 튜플을 차례로 반환한다.

In [45]:
 = ['봄', '여름', '가을', '겨울']
list(enumerate())
Out[45]:
[(0, '봄'), (1, '여름'), (2, '가을'), (3, '겨울')]

따라서 반복문에서 다음과 같이 다루면 편리하다.

In [48]:
 = ['영', '일', '이', '삼']
for ,  in enumerate():
    print("{}은(는) {}.".format(, ))
0은(는) 영.
1은(는) 일.
2은(는) 이.
3은(는) 삼.

sorted

sorted(<반복 가능 객체>)는 반복 가능 객체를 정렬해서 새로운 객체를 반환한다.

In [49]:
 = "안녕하세요."
sorted()
Out[49]:
['.', '녕', '세', '안', '요', '하']

zip

zip(<반복 가능 객체들>) 함수는 반복 가능 객체등의 성분을 차례로 순서쌍형식으로 반환한다.

In [50]:
리1 = [0, 1, 2, 3]
리2 = ['일', '이', '삼', '사']
for a, b in zip(리1, 리2):
    print("{}은(는) {}".format(a, b))
0은(는) 일
1은(는) 이
2은(는) 삼
3은(는) 사

zip 객체를 unzip할 수 있다.

In [52]:
스피드 = [('이', '승훈'), ('정', '재원'), ('이', '상화')]
, 이름 = zip(*스피드)

Out[52]:
('이', '정', '이')
In [53]:
이름
Out[53]:
('승훈', '재원', '상화')

reversed

reversed() 메소드는 열거형의 거꾸로 탐색한다. reversed는 생성자(generator)를 반환하기 때문에 for, list와 같은 문을 통해서 값을 구체화 할 수 있다.

In [56]:
 = ['하나', '둘', 3, '넷']
list(reversed())
Out[56]:
['넷', 3, '둘', '하나']

사전(dict)

사전은 열쇠-값(key-value) 순서쌍으로 이루어진 자료형이며 크기가 변경가능하다. 여기서 열쇠(key), 값(value)은 파이썬 객체이다. 사전을 생성하는 한가지 방법은 중괄호 {}를 이용하는 것이다.

In [57]:
빈사전 = {}
사전1 = {'ㅁ': '메밀', 'ㅂ': '보리'}
사전1
Out[57]:
{'ㅁ': '메밀', 'ㅂ': '보리'}

리스트와 튜플 같이 대괄호를 이용하여 성분에 접근할 수 있다.

In [58]:
사전1['ㅁ']
Out[58]:
'메밀'

또는 삽입할 수 있다.

In [59]:
사전1[3] = [1, 2, 3]
사전1
Out[59]:
{3: [1, 2, 3], 'ㅁ': '메밀', 'ㅂ': '보리'}

사전1[3]에서 3은 성분의 위치를 나타내는 인덱스가 아니고 사전의 열쇠이다. 열쇠가 사전에 있는지를 in 을 이용해서 알 수 있다.

In [60]:
3 in 사전1
Out[60]:
True

del를 이용해서 열쇠를 제거할 수 있다.

In [64]:
사전1['ㅋ'] = '캄캄한'
사전1
Out[64]:
{3: [1, 2, 3], 'ㅁ': '메밀', 'ㅂ': '보리', 'ㅋ': '캄캄한'}
In [65]:
del 사전1['ㅋ']
사전1
Out[65]:
{3: [1, 2, 3], 'ㅁ': '메밀', 'ㅂ': '보리'}

pop 메소드를 이용해서도 제거할 수 있는데 pop 메소드는 제거하면서 값을 반환한다.

In [66]:
사전1[4] = '아무것'
사전1
Out[66]:
{3: [1, 2, 3], 4: '아무것', 'ㅁ': '메밀', 'ㅂ': '보리'}
In [68]:
 = 사전1.pop(4)

Out[68]:
'아무것'
In [69]:
사전1
Out[69]:
{3: [1, 2, 3], 'ㅁ': '메밀', 'ㅂ': '보리'}

keys, values 메소드를 이용해서 열쇠와 값들에 대한 반복자(iterator)를 얻을 수 있다.

In [72]:
list(사전1.keys())
Out[72]:
['ㅁ', 'ㅂ', 3]
In [73]:
list(사전1.values())
Out[73]:
['메밀', '보리', [1, 2, 3]]

update() 메소드를 이용해 다른 사전 객체와 합칠 수 있다.

In [74]:
사전1.update({3: '하나, 둘, 셋', 4: '넷'})
사전1
Out[74]:
{3: '하나, 둘, 셋', 4: '넷', 'ㅁ': '메밀', 'ㅂ': '보리'}

기존 사전의 열쇠와 합쳐지는 사전의 열쇠가 같으면 기존의 값은 새로운 값으로 대체된다.

열거형으로부터 사전 만들기

가끔씩 두 개의 열거형으로부터 순서대로 쌍으로 사전 객체를 만들 필요가 생긴다. 다음과 같이 코딩을 할 수도 있다.

쌍 = {}
for 열쇠, 값 in zip(열쇠리스트, 값리스트):
    쌍[열쇠] = 값

하지만 사전은 두 개의 순서쌍으로 이루어진 자료형이므로 기본적으로 순서쌍 객체를 사전으로 만들 수 있다.

In [75]:
 = dict(zip(range(5), reversed(range(5))))

Out[75]:
{0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

나중에 사전 축약형을 사용한 간단한 사전 만드는 방법에 대해서 알아본다.

기본값(default values)

사전 객체를 다룰 때 사전에 열쇠가 있으면 해당하는 값을 가져오고 그렇지 않으면 기본값을 설정하는 것이 일반적이다.

if 열쇠 in 사전:
    값 = 사전[열쇠]
else:
    값 = 기본값

get 또는 pop 메소드를 이용하면 위의 문장을 간단히 다음과 같이 쓸 수 있다.

값 = 사전.get(열쇠, 기본값)

get 메소드는 열쇠가 없을 경우 None을 반환하지만 pop은 예외를 발생시킨다. 값을 설정하면서 그 값을 분류하는 작업이 필요할 때가 있다. 예를 들어 단어장에 있는 단어들을 단어의 첫글자별로 분류한다고 해보자.

In [76]:
단어장 = ['가정', '가상', '보리', '밀', '보검']
첫글자별 = {}
for 단어 in 단어장:
     = 단어[0]
    if  not in 첫글자별:
        첫글자별[] = [단어]
    else:
        첫글자별[].append(단어)
첫글자별
Out[76]:
{'가': ['가정', '가상'], '밀': ['밀'], '보': ['보리', '보검']}

위와 같은 과정을 setdefault 메소드를 이용하면 정확히 똑같은 일을 할 수 있다. setdefault(열쇠, 기본값) 형식으로 사전의 열쇠가 있으면 해당하는 값을 반환하고, 없으면 기본값을 값에 설정하고 기본값을 반환한다.

In [77]:
첫글자별 = {}
for 단어 in 단어장:
     = 단어[0]
    첫글자별.setdefault(, []).append(단어)
첫글자별
Out[77]:
{'가': ['가정', '가상'], '밀': ['밀'], '보': ['보리', '보검']}

유효한 사전 열쇠 형

사전의 값은 어떤 파이썬 형이 와도 상관없지만 열쇠의 형은 불변형이어야 한다. 즉, 문자열, 숫자형, 튜플등이 올 수 있고 리스트는 올 수 없다. 이것은 객체가 해시 가능 객체이어야 한다는 말이다.

In [78]:
사전 = {}
사전[[1,2]] = '오류'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-78-aa307b1c0910> in <module>()
      1 사전 = {}
----> 2 사전[[1,2]] = '오류'

TypeError: unhashable type: 'list'

집합(set)

집합은 순서가 없는 대상들의 모임이다. 집합은 set(<반복 가능 객체>) 또는 중괄호 {}를 이용해서 만들 수 있다.

In [80]:
 = set("안녕하세요")

Out[80]:
{'녕', '세', '안', '요', '하'}
In [82]:
 = {"안", "녕", 1, 2, 1}

Out[82]:
{1, 2, '녕', '안'}

집합은 합집합, 교집합, 차집합 등과 같은 수학 연산을 할 수 있다. 합집합은 union 또는 |을 이용할 수 있다.

In [83]:
 = {1, 2, 3, 4}
 = {1, 3, 5, 7, 9}
.union()
Out[83]:
{1, 2, 3, 4, 5, 7, 9}
In [84]:
 | 
Out[84]:
{1, 2, 3, 4, 5, 7, 9}

교집합은 intersection 또는 &를 이용할 수 있다.

In [85]:
.intersection()
Out[85]:
{1, 3}
In [86]:
 & 
Out[86]:
{1, 3}

다음은 집합에서 사용되는 연산이다.

함수 다른 표현 설명
a.add(x) 없음 집합 a에 성분 x 추가.
a.clear() 없음 공집합으로 만든다.
a.remove(x) 없음 집합 a에서 성분 x 제거.
a.pop() 없음 집합 a에서 임의의 성분 제거. 공집합이면 KeyError 발생.
a.union(b) a | b 집합 ab의 합집합. 새로운 집합 객체 반환.
a.update(b) a |= b 집합 ab를 합집합하여 a를 갱신.
a.intersection(b) a & b 집합 ab의 교집합. 새로운 집합 객체 반환.
a.intersection_update(b) a &= b 집합 ab를 교집합하여 a를 갱신.
a.difference(b) a - b ab. 새로운 객체 반환.
a.difference_update(b) a -= b ab해서 a를 갱신.
a.symmetric_difference(b) a ^ b a 합집합 b에서 a 교집합 b를 뺀 집합. 새로운 객체 반환
a.symmetric_difference_update(b) a ^= b a 합집합 b에서 a 교집합 b를 뺀 집합으로 a를 갱신
a.issubset(b) 없음 ab의 부분집합이면 참
a.issuperset(b) 없음 ba의 부분집합이면 참
a.isdisjoint(b) 없음 ab의 교집합이 공집합이면 참

집합은 튜플과 마찬가지로 불변이다. 집합을 리스트로 변경하려면 list() 함수를 사용하면 된다.

In [87]:
 = {1, 2, 3}
list()
Out[87]:
[1, 2, 3]

두 집합이 같다는 것은 성분이 같으면 된다.

In [88]:
{1, 2, 3} == {3, 2, 1}
Out[88]:
True

리스트, 집합, 사전 축약형

리스트 축약은 간편하게 리스트를 만드는데 사용되는 파이썬 용법이다. 다음과 같은 형식으로 사용된다.

[식 for 변수 in <반복 가능 객체> if <참 거짓 판별 식>]

이것은 다음과 같은 코드와 같다.

결과 = []
for 변수 in <반복 가능 객체>:
    if <참 거짓 판별 식>:
        결과.append(식)

if 문 뒤에 은 생략 가능하다. 예를 들면 문자열 리스트 중에서 문자열의 크기가 3이상인 문자열을 대문자로 바꾸어 리스트로 만드는 것을 리스트 축약으로 표현하면 다음과 같다.

In [90]:
 = ['a', 'as', 'list', 'paragraph', 'rabbit', 'to']
[x.upper() for x in  if len(x) > 2]
Out[90]:
['LIST', 'PARAGRAPH', 'RABBIT']

사전과 집합 축약형도 리스트 축약형과 비슷하다. 사전 축약형은 다음과 같다.

{열쇠식: 값식 for 변수 in <반복 가능 객체> if <참 거짓 판별 식>}

집합 축약형은 다음과 같다.

{식 for 변수 in <반복 가능 객체> if <참 거짓 판별 식>}

앞 예제에서 문자열의 크기에 대한 집합을 집합 축약형을 이용하면 다음과 같다.

In [91]:
{len() for  in }
Out[91]:
{1, 2, 4, 6, 9}

이것은 map 함수를 이용하면 쉽게 구할 수도 있다.

In [92]:
set(map(len, ))
Out[92]:
{1, 2, 4, 6, 9}

사전 축약형을 이용해서 문자열과 인덱스에 대한 사전을 만들 수 있다.

In [95]:
{: 인덱스 for 인덱스,  in enumerate()}
Out[95]:
{'a': 0, 'as': 1, 'list': 2, 'paragraph': 3, 'rabbit': 4, 'to': 5}

중첩 리스트 축약

리스트들을 원소로 갖는 리스트가 다음과 같이 있다

In [99]:
먹거리 = [['사과', '배', '바나나', '파인애플'], ['감자', '고구마', '옥수수', '칡']]

먹거리 리스트 중에서 문자열의 길이가 3이상인 문자열 리스트를 만들기 위해 다음과 같이 할 수 있다.

In [101]:
관심 = []
for 음식들 in 먹거리:
     = [ for  in 음식들 if len() > 2]
    관심.extend()
관심
Out[101]:
['바나나', '파인애플', '고구마', '옥수수']

이것을 중첩 리스트 축약을 이용해서 다음과 같이 쓸 수 있다.

In [102]:
[ for 음식들 in 먹거리 for  in 음식들 if len() > 2]
Out[102]:
['바나나', '파인애플', '고구마', '옥수수']

튜플들의 리스트를 하나의 리스트로 펼칠 수 있다.

In [103]:
튜리 = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
[원소 for  in 튜리 for 원소 in ]
Out[103]:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

함수

함수는 파이썬에서 코드를 구성하고 재사용하는데 있어서 사용되는 주요 방법이다. 일반적으로 두 번이상 사용되는 기능이 있다면 그 기능에 해당되는 함수를 만들어 사용하는 것이 효율적이다. 함수는 def 예약어를 사용해서 만든다.

In [104]:
def 내함수(매개1, 매개2, 매개3=1.5):
    if 매개3 > 1:
        return (매개1 + 매개2) / 매개3
    else:
        return (매개1 + 매개2) * 매개3

return 값이 없으면 None이 반환된다. 함수는 위치 매개변수(positional parameter)와 핵심어 매개변수(keyword parameter)로 이루어진다. 위에서 매개1, 매개2는 위치 매개변수이고, 매개3는 핵심어 매개변수이다. 함수를 호출할 때 정의된 매개변수에 대응되는 값들을 넘겨주는데 넘겨주는 값을 인자(arguent)라고 한다. 따라서 인자도 매개변수와 마찬가지로 위치 인자(positional argument)와 핵심어 인자(keyword argument)로 불린다.

In [ ]:
내함수(2, 3, 매개3=5)
내함수(2, 3, 5)
내함수(2, 3)

핵심어 인자는 선택 인자와 기본값 설정을 할 때 주로 쓰인다. 여기서 주의해야 할 것은 위치 인자들은 반드시 핵심어 인자들 앞에 위치해 있어야 한다는 것이다. 핵심어 인자들을 사용할 때는 인자의 순서는 무시될 수 있다. 다음은 모두 같은 결과를 얻는다.

In [106]:
내함수(매개3=5, 매개1=2, 매개2=3)
내함수(매개2=3, 매개1=2, 매개3=5)
Out[106]:
1.0

이름공간, 변수 범위

변수는 지역변수(local variable)와 전역변수(global variable), 비지역변수(nonlocal varaiable)로 나눌 수 있다. 지역변수란 함수 안에서 정의된 변수를 말하고 전역변수란 함수 밖에서 정의된 변수를 의미한다. 비지역변수는 전역변수도 지역변수도 아닌 변수를 의미한다. 함수 안의 내부 함수에서 선언을 하며 내부 함수 밖의 있는 함수의 변수를 전역 변수같이 사용할 수 있다. 이름공간(namespace)은 변수들의 유효 범위를 일컫는 말로 지역 공간, 전역 공간, 내장 공간(built-in namespace)으로 구분할 수 있다. 이름공간은 식별자들이 지역, 전역 또는 내장 영역에 있는 것인지를 구분하는 것이다. 지역은 locals(), 전역은 globals(), 내장 영역은 dir(__builtin__) 함수를 이용해서 식별자들을 알아 볼 수 있다.

다음은 지역 변수의 예를 든 것이다.

def 함수():
    변 = []
    for i in range(5):
        변.append(i)

위에서 은 지역변수로서 함수 안에서만 작동을하고 함수를 벗어나면 사라진다. 그렇지만 다음과 같은 경우는 전역변수로서 함수와 상관없이 작동하며 어디에서나 접근가능한 변수로 함수 안에서 접근할 수 있으며 그 내용을 바꿀 수 있다. 이러한 것을 부작용(side-effect)이라 하며 이러한 것은 피해야 한다.

변 = []

def 함수():
    for i in range(5):
        변.append(i)

만일 전역변수를 함수 내에서 사용하려면 다음과 같이 global 예약어를 이용하여 전역이라는 것을 명시하고 사용하는 것이 좋다.

변 = []

def 함수():
    global 변

    for i in range(5):
        변.append(i)

하지만 되도록이면 global을 사용하지 않는 것이 좋다.

다중 값 반환

함수의 반환값으로 여러 개의 값을 반환하고자 할 때는 튜플 또는 사전형으로 값들을 감싸서 반환하면 된다. 다음은 튜플을 이용해서 3개의 값을 반환하는 예이다.

def 함수():
    a = 1
    b = 2
    c = 3
    return (a, b, c)

함수를 호출해서 값을 받을 때는 튜플 풀기(unpacking)를 이용할 수 있다.

a, b, c = 함수()

또는 사전형으로 감싸서 보낼 수 있다.

def 함수():
    a = 1
    b = 2
    c = 3
    return {'a':a, 'b':b, 'c':c}

함수 객체

다음과 같은 도시 이름들에 대한 자료를 분석에 사용될 수 있도록 정리하려고 한다.

In [3]:
도시 = ['대전', '   천안', '조치원!', '서울', '인천###', '강릉  ', '서귀포?', 'DAegu']

일반적으로 사용자들로부터 입력받은 자료들은 위와 같이 바로 분석에 들어갈 수 있는 상태가 아니다. 분석에 사용될 수 있도록 많은 작업들이 필요하다. 공백 문자 제거, 물음표, 느낌표 등 불필요한 기호들 제거, 대소문자 통일등의 작업이 필요하다. 이러한 작업을 할 수 있는 모듈로 정규표현식 모듈인 re를 이용할 수 있다.

In [8]:
import re

def 문자열정리(문자열):
     = []
    for  in 문자열:
         = .strip() # 앞, 뒤 공백 문자 제거
         = re.sub('[!#?]', '', ) # 불필요한 기호 제거
         = .title() # 영단어 대문자로 시작
        .append()
    return 

결과는 다음과 같다.

In [9]:
문자열정리(도시)
Out[9]:
['대전', '천안', '조치원', '서울', '인천', '강릉', '서귀포', 'Daegu']

다른 방법으로는 각각의 함수를 리스트로 만들어 작업하는 것이다.

In [10]:
def 불필요한기호제거(문자열):
    return re.sub('[!#?]', '', 문자열)

작업함수들 = [str.strip, 불필요한기호제거, str.title]

def 문자열정리2(문자열, 함수들):
     = []
    for  in 문자열:
        for 함수 in 함수들:
             = 함수()
        .append()
    return 

다음과 같은 결과를 얻는다.

In [12]:
문자열정리2(도시, 작업함수들)
Out[12]:
['대전', '천안', '조치원', '서울', '인천', '강릉', '서귀포', 'Daegu']

내장함수 map을 이용하여 리스트 항목들에 대해서 주어진 함수를 차례로 실행할 수 있다.

In [14]:
list(map(불필요한기호제거, 도시))
Out[14]:
['대전', '   천안', '조치원', '서울', '인천', '강릉  ', '서귀포', 'DAegu']

익명함수(lambda)

파이썬은 한 문장으로 함수를 만들 수 있는 익명함수(또는 람다(lambda)함수)를 제공한다. lambda 예약어를 이용해 만들 수 있다. 다음과 같은 간단한 함수를 만들 때 람다함수를 이용하면 편리하다.

def 간단함수(매):
    return 2 * 매

이것을 람다함수로 작성하면 다음과 같다.

익명함수 = lambda 매 : 2 * 매

람다함수는 자료 분석하는데 편리하게 사용된다. 왜냐면 많은 자료 변환 함수들은 함수를 인자로 받기 때문이다. 다음과 같은 예를 보자.

In [15]:
def 리스트함수적용(리스트, 함수):
    return [함수() for  in 리스트]

 = [1, 2, 3, 4, 5]
리스트함수적용(, lambda : 2 * )
Out[15]:
[2, 4, 6, 8, 10]

또 다른 예로는 문자열 리스트의 문자가 서로 다른 문자의 갯수에 대해서 정렬하려고 한다.

In [16]:
 = ['foo', 'card', 'bar', 'aaaa', 'abab']

리스트 sort 메소드와 람다함수를 이용하자.

In [18]:
.sort(key=lambda x: len(set(list(x))))

Out[18]:
['aaaa', 'foo', 'abab', 'bar', 'card']

직접하기

  • 다음 조건을 만족하는 람다함수들을 각가 만드시오.
    1. 숫자 하나를 받아 그 값의 제곱을 반환한다.
    2. 숫자 두 개를 받아 두 수의 제곱의 합의 제곱근을 반환한다.
    3. 임의의 숫자들을 받아 그 수들의 평균을 반환한다.
    4. 하나의 문자열을 입력받아 그 문자열 중에 반복되지 않는 문자들만으로 이루어진 문자열을 반환한다.

생성자(generator)

리스트, 튜플, 문자열등과 같은 열거형에서 for문을 이용해서 반복할 수 있는 것은 열거형이 반복자 규칙을 따르기 때문이다. 사전도 열쇠들에 대한 반복자를 반환한다.

In [19]:
 = {'ㄱ': '가', 'ㄴ': '나', 'ㄷ': '다'}

for 열쇠 in :
    print(열쇠)
ㄱ
ㄴ
ㄷ

파이썬 인터프리터는 for 열쇠 in 에서 in 뒤에 있는 객체 에서 반복자를 생성한다. iter 함수를 이용해서 반복가능객체에서 반복자를 반환받을 수 있다. 반복가능 객체란 __iter__() 메소드를 구현한 객체를 말하며 반복자(iterator)를 반환해야 한다. 반복자란 __next__() 메소드를 구현한 객체를 의미한다.

In [20]:
사전반복자 = iter()
사전반복자
Out[20]:
<dict_keyiterator at 0x262111547c8>

생성자(generator)는 반복자를 만들 수 있는 방법이다. 생성자란 yield 예약어가 들어간 함수로 next 함수가 불릴 적마다 실행이 된다. yield는 함수에서 return과같이 yield 뒤에 있는 값을 반환하고 실행을 멈추고 있다가 next가 불리면 그 뒤로 실행을 다시 시작한다.

In [21]:
def 생성자예제(n=10):
    print("1부터 {}까지 제곱수를 출력합니다.".format(n ** 2))
    for i in range(1, n+1):
        yield i ** 2

생성자를 호출해도 곧바로 값을 반환하지는 않는다.

In [23]:
 = 생성자예제()

Out[23]:
<generator object 생성자예제 at 0x00000262111313B8>

for 문과 같이 생성자에게 값을 요청할 때만 값을 반환한다.

In [24]:
for  in :
    print()
1부터 100까지 제곱수를 출력합니다.
1
4
9
16
25
36
49
64
81
100

직접하기

  • 정수 n을 인자로 받아서 n부터 0까지 반환하는 생성자 함수 거꾸로(n)를 만드시오. for 문을 이용해서 생성자 함수를 테스트하시오.

생성자 축약식

리스트, 사전, 집합 축약식과 비슷하게 생성자 축약식을 이용해 생성자를 만들 수 있다. 생성자 축약식은 소괄호를 이용해서 만든다.

In [25]:
 = (x ** 2 for x in range(10))

Out[25]:
<generator object <genexpr> at 0x000002621109C5C8>

이것은 다음과 같다.

In [28]:
def 생성자만들기():
    for x in range(10):
        yield x ** 2

 = 생성자만들기()

Out[28]:
<generator object 생성자만들기 at 0x0000026211131888>

생성자 축약식은 리스트 인자를 대신해서 사용될 수 있다.

In [32]:
 = [1, 4, 9, 16, 25]
sum()
Out[32]:
55
In [33]:
sum(x **2 for x in range(6))
Out[33]:
55
In [35]:
dict(("{}".format(x), x ** 2) for x in range(5))
Out[35]:
{'0': 0, '1': 1, '2': 4, '3': 9, '4': 16}

오류와 예외 처리

파이썬에서 오류 또는 예외(exceptions)를 잘 다룰수 있으면 프로그램의 완성도를 높일 수 있다. 데이터 분석 문제를 다룰 때 많은 함수들은 지정된 형식의 자료형만 인자로 받는다. 예를 들어 파이썬 float 함수는 부동소수점형의 문자열만 인자로 받아서 변환할 수 있다. 다른 입력이 들어오면 ValueError를 출력한다.

In [1]:
float('1.234')
Out[1]:
1.234
In [2]:
float('a12')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-a83f98d7bcb4> in <module>()
----> 1 float('a12')

ValueError: could not convert string to float: 'a12'

잘못된 형식의 입력이 인자로 건네질 때 오류를 출력하지 않고 잘못 건네진 인자를 출력하기 위해서는 다음과 같이 한다.

In [3]:
def 부동형입력():
    try:
        return float()
    except:
        return 

float(부) 에서 오류가 발생하면 except 절을 실행하게 된다.

In [4]:
부동형입력(1.234)
Out[4]:
1.234
In [5]:
부동형입력('ㅁ213.4')
Out[5]:
'ㅁ213.4'

다른 오류가 발생할 수도 있다.

In [6]:
float((1,2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-a101b3a3d6cd> in <module>()
----> 1 float((1,2))

TypeError: float() argument must be a string or a number, not 'tuple'

ValueError는 무시하고 TypeError를 발생시키고 싶으면 다음과 같이 한다.

In [7]:
def 부동형입력():
    try:
        return float()
    except ValueError:
        return 
In [8]:
부동형입력('ㅁ123.3')
Out[8]:
'ㅁ123.3'
In [9]:
부동형입력((1,2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-fd25d904d9eb> in <module>()
----> 1 부동형입력((1,2))

<ipython-input-7-9a19f49b9c46> in 부동형입력(부)
      1 def 부동형입력():
      2     try:
----> 3         return float()
      4     except ValueError:
      5         returnTypeError: float() argument must be a string or a number, not 'tuple'

두 개의 오류를 모두 처리하고 싶으면 다음과 튜플로 처리하면 된다.

In [10]:
def 부동형입력():
    try:
        return float()
    except (ValueError, TypeError):
        return 
In [11]:
부동형입력((1, 2))
Out[11]:
(1, 2)

try 절에서 예외가 발생했건 안했건 항상 실행시켜야할 문장이 있으면 finally 절을 이용한다. 일반적으로 파일이나 데이터베이스 연결을 해제하거나 닫을 때 사용한다.

f = open(path, 'w')

try:
    파일에쓰는작업(f)
finally:
    f.close()

직접하기

  • 다음 코드에서 잘못된 형을 입력했을 때 올바른 값이 입력될 때까지 안내문을 계속 반복하도록 만드시오.

    고객 = {}
    
    for 항목 in ["이름", "나이", "키", "몸무게"]:
        고객[항목] = input("{}를(을) 입력하세요: ".format(항목))
    
  • 다음은 사전에 리스트 이름에 해당하는 항목 리스트를 만들어 항목을 추가하는 코드이다. 사전에 리스트 이름이 없다면 KeyError가 발생하는데 이 오류를 try-except 문을 이용해서 잡는 코드로 바꾸시오.

    def 사전에리스트항목추가(사전, 리스트이름, 항목):
        if 리스트이름 in 사전:
            항 = 사전[리스트이름]
            print("{} 은 {} 항목을 가지고 있습니다.".format(리스트이름, len(항)))
        else:
            사전[리스트이름] = []
            print("{} 리스트를 새로 만들었습니다.".format(리스트이름))
    
        사전[리스트이름].append(항목)
    
        print("{}에 {}를 추가했습니다.".format(리스트이름, 항목))
    

파일과 운영체제

파일을 읽고 쓰기 위해서는 내장함수 open 함수를 이용한다. open 사용법은 다음과 같다.

open('경로', 모드='r', encoding=None)

경로는 파일 위치를 나타내고, 모드는 읽기(r), 쓰기(w)등을 지정할 수 있다. encoding은 기본값으로는 locale.getpreferredencoding()을 사용한다. 파일 내용을 읽기 위해서는 다음과 같이 with 문을 사용하고 반복 가능 객체인 파일을 이용하여 한 줄씩 읽을 수 있다.

In [14]:
with open('examples/참좋은말.txt', encoding='utf-8') as f:
    for line in f:
        print(line, end='')
내 몸에서 가장 강한 것은 혀
한잎의 혀로
참, 좋은 말을 쓴다

미소를 한 육백개나 가지고 싶다는 말
네가 웃는 것으로 세상 끝났으면 좋겠다는 말
오늘 죽을 사람처럼 사랑하라는
... 생략 ...

파일을 열었으면 close 메소드를 이용해 반드시 닫아주어야 한다. with 문은 close 메소드를 사용하지 않아도 자동으로 파일 자원을 닫아 준다. 아래 표는 read/write 모드에서 자주 사용되는 메소드들이다.

모드 설명
r 읽기 전용 모드
w 쓰기 전용 모드. 새로운 파일을 만든다.(같은 이름의 파일이 있으면 지운다)
a 기존 파일에 추가한다.(파일이 없으면 새로 만든다.)
r+ 읽기와 쓰기 둘 다 가능하다.
b 바이너리 파일 모드로 읽고 쓴다. rb 또는 wb
t 텍스트 형식으로 읽고 쓴다. rt 또는 wt

다음은 파일 메소드들이다.

메소드 설명
read([크기]) 모드에 따라 크기가 주어지면 크기만큼의 바이트 또는 글자수를 읽어온다. 크기가 없으면 파일 전체를 읽어 온다.
readlines([크기]) 크기 줄 만큼을 읽어온다. 그렇지 않으면 파일 전부를 줄 단위로 읽어 온다.
write(문자열) 문자열을 파일에 쓴다.
writelines(문자열리스트) 줄 단위의 문자열 리스트를 파일에 쓴다.
close() 파일 자원을 닫는다.
flush() 버퍼에 있는 것을 디스크에 쓴다.
seek(위치) 주어진 위치로 스트림을 이동한다.
closed 파일 자원이 닫혔으면 참(True) 반환

직접하기

  • 인터넷에서 좋아하는 시를 복사하여 파일로 저장하고 저장한 파일을 불러와 출력을 하시오.