[Python101] Iterable (2) – 리스트 – 2

오늘은 리스트의 다른 활용을 살펴보는 두 번째 시간이다. 지난 강좌 글에서 이번에는 리스트와 문자열을 어떻게 변환하는지 살펴보겠다고 했고, 또 지능형 리스트에 대해서도 알아보기로 했다.

문자열과 리스트

문자열을 리스트로 처리하는 방법은 특히 ‘저장된 데이터들’을 처리하기에 편리하다. 즉 어떤 일련의 데이터를 리스트로 다루다가 이를 파일에 저장할 때는 문자열로 기록한다음, 다시 꺼내서 사용할 때는 다시 리스트로 분리하여 사용할 수 있는 것이다.

문자열을 리스트로, 혹은 그 반대로 사용하는 변환하는 방법은 다음과 같은 것들이 있다.

  • 문자열을 한 글자, 한글자로 쪼개어 리스트로 만든다 : list() 함수
  • 문자열속에 특정한 구분자를 통해 여러개의 문자열로 나누어 리스트로 만든다. : split() 메소드
  • 리스트의 원소들을 문자열로 바꾸어 특정한 구분자를 끼워넣어 결합한다. : join() 메소드

대화형 쉘을 실행하고 다음 명령들을 입력해보자.

>>> a = 'copyright'
>>> b = 'It is not safe to modify the sequence being iterated over in loop."
>>> c = range(0, 50, 3)

>>> a
#--> 'copyright'
>>> la = list(a)
>>> la
#--> ['c','o','p','y','r','i','g','h','t']

>>> lb = b.split('\s')
>>> lb
#--> ['It', 'is', 'not', 'safe', 'to', 'modify', 'the', 'sequence', 'being', 'iterated', 'over', 'in', 'loop.']

>>> d = ','.join(lb)
>>> d
#--> 'It,is,not,safe,to,modify,the,sequence,being,iterated,over,in,loop.'

split()은 문자열의 메소드이다. 이는 인수로 받은 글자를 기준으로 문자열을 쪼개어 리스트로 만들어준다. 즉 위의 예제에서는 긴 문장에 대해 공백문자를 기준으로 분리한다고 했다. 그리고 그 결과는 lb에 대입하였고, lb를 통해서 만들어진 리스트를 확인할 수 있었다.

거꾸로 join() 역시 문자열의 메소드인데, 구분자로 사용할 글자를 주고 이 글자의 join 메소드를 사용하면 리스트의 원소를 다시 결합할 수 있다. d를 통해 이를 확인할 수 있었다.

지능형 리스트

위의 예제에서 c는 왜 만들었을까? 지금까지 보았던 range() 함수의 사용과는 조금 색다른데가 있다. 즉 시작값, 끝값 그 뒤에 뭔가 추가적인 인수를 더 썼다. 궁금증은 help를 통해 알아보면 된다. help(range) 라고 하면 아래와 같이 설명을 볼 수 있는데,

Help on built-in function range in module __builtin__:

range(...)
    range([start,] stop[, step]) -> list of integers

    Return a list containing an arithmetic progression of integers.
    range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
    When step is given, it specifies the increment (or decrement).
    For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!
    These are exactly the valid indices for a list of 4 elements.

range( [start], stop [, step]) 이라고 되어 있다. 즉 시작값과 끝값, 그리고 그 다음은 “간격”이다. 그리고 중요한 것 하나. 이렇게 함수 도움말에서 인자에 [대괄호]로 묶은 부분이 있다면 이 값은 필수가 아니라는 점이다. range() 함수에서는 오직 stop 만이 필수로 들어가는 값이다.

즉 함수가 인자로 받는 인수의 개수가 동적으로 바뀔 수 있다는 점인데, 이는 다른 언어에서는 허용하지 않는 경우가 많다. 이는 파이썬의 유연성을 높이는 굉장히 멋진 기능이다!

어쨌든 위의 예제에서 c는 0에서 시작하여 50보다 작은 3의 배수들을 모두 모아보았다. 이 들을 그냥 0, 1, 2, 3…  이런 식으로 다시 만들 수 있을까? 다음과 같은 코드를 생각해 볼 수 있다.

f = []
for x in c:
   f.append(x/3)

즉 c의 각 원소 x에 대해서 3으로 나눈 다음 그 결과를 f에 하나씩 밀어넣으면 된다. f 값을 확인해보면 결과를 확인해볼 수 있다.

그런데 이런 처리는 의외로 파이썬에서 종종 쓰인다. 그래서 파이썬은 ‘지능형 리스트’라는 걸 사용하는데 다음과 같이 사용한다.

[ {변수명}을 사용하는 서식 for {변수명} in {리스트명} ]

즉 f는 위의 코드가 아닌 단 한줄의 코드로 정리할 수 있다.

f = [num/3 for num in c]

지능형 리스트는 자주 쓰이지 않는 리스트 변환 작업을 따로 함수로 빼지 않고 다른 구문 내에서 간단히 처리하고자 할 때 쓰인다. 사실 지능형 리스트의 활용 방법은 조금 더 있지만, 그건 나중에 필요할 때 다시 언급하기로 하고, 여기서는 간단히 이런게 있다는 것만 설명하고 넘어가도록 하자.

[Python101] Iterable (1) – 리스트

지난 시간 for 문을 설명할 때 다음과 같은 문법이 잠깐 등장했다.

for number in range(1,10):

이 구문은 range() 함수를 사용해 만들어지는 1~9 까지의 숫자의 ‘집합’의 개별 원소에 대해 반복적인 명령을 수행하는 구문이라고 했다. 이러한 집합은 사실 영어로 ‘iterable’이라고 하지만 우리말로는 딱히 정확히 대응시킬만한 말이 없다는 것도 이야기했다. 이번 시간에는 이 iterable에 대해서 알아보고자 한다.

* 이번 시간은 IDLE의 대화형 쉘을 통해 직접 확인해보면서 배우는 것이 좋다.

지난 시간에서 사용한 ‘집합’이라는 표현이 어찌보면 가장 근접한 표현일 수도 있다. iterable이라는 말은 ‘집합’ 그 자체보다는 “개별 원소를 반복적으로 셀 수 있는”이라는 문맥적인 의미가 있기 때문이다. 파이썬에서 사용하는 집합에는 1) 리스트, 2) 튜플, 3) 사전(dictionary) 가 있다. 각각은 약간의 차이점은 있으나, 대체로 for 문과 같은 반복 작업에서 원소들을 일일이 열거 한다는 점에서 공통점을 가진다.

리스트

프로그래밍 언어를 배울 때 빠지지 않고 등장하는 개념이 있으니, 바로 ‘배열(array)’이다. 배열은 수학에서의 ‘집합’과 매우 유사하기도 한데, 파이썬의 리스트는 바로 이 배열과 거의 같은 개념이라 볼 수 있다.

리스트는 여러 개의 원소를 포함하는 하나의 집합체이다. 리스트에 포함되는 개별 값들을 원소라 할 수 있는데, 이 원소들은 모두 일정한 순서를 가지고 있다. 특정한 원소의 순서를 ‘인덱스’라고 한다.

리스트는 대괄호로 여러 값들을 연속해서 둘러 싸 만들 수 있다. 이 때 각 값들은 컴마(,)로 구분된다.

a  = [2,3,5,7,11,13,17,19]

a 라는 변수에 20보다 작은 소수(prime number)로 구성된 리스트를 만들어 대입했다. 이 때 작은 수 부터 큰 수의 순서로 쓴 것은 단지 편의의 문제일 뿐이다.

b = [5,19,2,7,13,11,3,19]

와 같이 불규칙한 순서를 써서 만든 리스트 b는 구성하고 있는 원소는 a와 같지만 각각의 원소의 순서가 다르다. (따라서 둘은 완전히 다른 리스트이다.)

하나의 원소의 인덱스

리스트의 각 원소는 정해진 순서가 있다고 했다. 이 때의 순서값을 ‘인덱스’라고 하며, 인덱스는 0부터 시작한다. 즉 첫번째 원소의 인덱스는 항상 0 이다.

리스트로부터 특정한 인덱스에 위치한 원소를 지정하려면 리스트이름[인덱스]와 같은 식으로 접근하게 된다.

print a[2]
#==> 5

마찬가지로 c = a[5] 와 같은 식으로 특정 원소를 다른 변수에 대입하는 것도 가능하다.

파이썬의 리스트는 재미있게도, 독특한 인덱스를 취급한다. 바로 음수 인덱스이다.

print a[2]
print a[-2]

위 명령은 리스트 a의 뒤에서 두 번째 원소를 가리킨다. 즉 17을 출력하게 된다.

리스트의 부분집합

리스트는 수학적 개념의 ‘집합’과도 매우 유사하다고 했다. 그리고 인덱스를 사용해서 특정한 원소에 접근하는 것이 가능하다고도 했다. 이와 마찬가지로 인덱스를 사용하여 부분 집합을 정의할 수 있다.

c = a[2:5]
print c

위의 코드는 리스트 a로부터 부분집합인 c를 추출하는 과정을 보여준다. a[2:5]는 2번째 인덱스에서 5번 인덱스까지를 말하는데, 주의할 것은 뒤쪽 인덱스는 포함하지 않는다. 즉 2번, 3번, 4번의 인덱스에 해당하는 원소만이 추출되는 것을 확인할 수 있다.

만약 :을 쓰고 한쪽을 비운다면 끝까지에 해당한다.

d = a[3:] # -> [7, 11, 13,19]
#인덱스           3   4   5  6
e = a[:5] # -> [2, 3, 5, 7, 11]
#인덱스           0  1  2  3   4

이 때도 뒤쪽 인덱스는 포함하지 않는 다는 점에 주의하자. 이는 range() 함수에서도 동일하게 적용되었다. range(2,10) 은 2에서부터 숫자 범위를 리스트로 만들어서 반환해 준다. 이 때 뒤쪽에 들어가는 10은 포함되지 않아서 9까지만 들어가게 된다. 여기서 중요한 것! 바로 range()  함수가 결과값을 list로 반환해준다는 것이다.

(시작값, 끝값) ==> range() ==> [시작값 ~ 끝 값]으로 된 리스트

문자열과 리스트

list는 리스트를 지칭하는 파이썬의 예약된 단어이다. “리스트라는 데이터 타입”을 의미한다. 같은 이름의 함수인 list()는 특정한 객체를 리스트로 만들어준다. 문자열은 한글자, 한글자의 문자가 이어져서 단어나 문장이 된 텍스트 정보를 의미하는데, 이는 한글자씩으로 만들어진 리스트와 비슷하지 않은가?

str = 'elephant'
g = list(str)
print g
# ['e','l','e','p','h','a','n','t']

문자열과 리스트를 오가는 표현은 이후로도 자주 등장할 것인데, 아마도 별도의 챕터로 분리해서 설명하는 것이 좋겠다는 생각이 든다.

list 는 파이썬에서 “리스트라는 데이터 타입”을 의미한다고 했다. 따라서  dir(list)라고 해보면 리스트가 가지고 있는 기능들을 열람할 수 있을 것이다.

dir() 명령을 통해 확인할 수 있는 리스트의 동작은 다음과 같다. (각각의 명령에 대해서 도움말은 help(list.pop) 과 같은 식으로 찾아볼 수 있다.

  • append(x) : 리스트의 끝에 새로운 원소 x를 추가한다.
  • count(x) :  리스트에서 x 라는 원소가 몇 번 들어있는 지 세어본다.
  • extend(x) : 리스트에 새로운 리스트 x를 연결해준다.
  • index(x) : 리스트에서 x라는 원소의 인덱스를 구해준다. 이 때 x가 두 개 이상 들어있다면, 맨 처음 x만 찾는다.
  • insert(인덱스, x) : 현재 리스트의 주어진 인덱스 위치에 x라는 원소를 끼워넣어준다.
  • pop() : 리스트의 맨 마지막 원소를 반환하고, 해당 원소를 원래 리스트에서 제거한다. 만약 pop(x) 라고 하면 x를 반환하고, 리스트는 x를 제거한다.
  • remove(x) : 리스트에 포함된 원소 x를 제거한다. (pop과는 달리 뭔가 반환하지는 않는다.)
  • reverse() : 리스트를 역순으로 바꾼다.
  • sort() : 리스트의 원소들을 정렬한다.

리스트를 다루는 함수

리스트 자체가 제공하는 함수는 ‘메소드’라고 부른다. reverse, pop, sort 등은 리스트의 메소드이다. 앞서 정의한 a의 경우에는 다음과 같이 실행해 볼 수 있다.

a.reverse()
print a # --> [19, 17, 13, 11, 7, 5, 3, 2]
a.pop()
#--> 2
print a #--> [19, 17, 14, 11, 7, 5, 3]
a.sort()
print a #--> [3, 5, 7, 11, 13, 17, 19]

이러한 리스트 자체의 메소드 외에도 몇 가지 리스트와 관련된 기본 함수들이 있다. 이 중에서 가장 자주 쓰이는 것은 len()sorted() 함수이다. len 함수는 리스트의 원소의 총 개수를 (즉 리스트의 길이를) 반환하고, sorted() 함수는 인자로 받은 리스트를 정렬한 사본을 반환한다. sorted() 함수는 원본 리스트의 원소의 순서를 바꾸지 않는다. 반면, 리스트의 list.sort() 메소드는 원본 리스트의 순서를 바꾼다.

그리고 list()라는 함수는 위에서도 잠깐 살펴보았지만, 리스트로 변경이 가능한 데이터형을 쪼개거나 변환하여 리스트로 만들어 반환한다. 문자열을 리스트로 바꾸거나 다른 “집합” 형식인 튜플을 리스트로 바꿀 수 있다.

리스트의 효용

다른 프로그래밍 언어에서도 배열은 매우 중요한 데이터 형식으로 취급한다. 일련의 데이터를 한 덩어리로 다루거나, 목록으로 관리하거나 하는 등 실질적인 어플을 만들 때 상당히 많이 적용된다.

또한 “스택”이나 “큐”와 같은 개념 역시 배열을 이용해서 구현한다. (스택이나 큐는 다른 글을 통해서 알아보도록 하자. 혹은 구글에서 검색을 해봐도 좋다.)

또한 문자열과 리스트를 서로 변환해가면서 처리하는 것 역시 매우 유용하게 활용된다.

다음 시간에는 리스트를 문자열과 어떻게 함께 사용하여 활용하는지, 그리고 조금 더 멋진 “지능형 리스트”란 무엇인지를 잠깐 살펴보겠다.