파이썬은 처음이라 – 연속열은 처음이라

이번 시간에는 지난 번에 살짝 언급만 하고 넘어갔던 튜플에 대해서 기본적인 내용을 다루겠다. 튜플은 파고 들자면 제법 묵직해질 수 있는 토픽이기는 하지만, 튜플을 활용하는 화려한(?) 기법들은 개인적으로 중급 이상의 과정에 어울린다고 생각하기 때문에 여기서는 간단한 개념과 기본적인 사용법에 대해서만 설명하고자 한다. 튜플은 여러가지 측면에서 리스트와 비슷한 점이 많고, 실제로 리스트와 크게 구분없이 쓰이는 경향이 있는 것도 사실이다. 리스트와 튜플의 공통적인 특성과 이런 특성을 가지는 타입들을 부르는 말인 연속열에 대해서 이야기해보고자 한다.

튜플은 처음이라

튜플은 여러 개의 값을 묶어서 마치 하나의 값처럼 다룰 수 있는 데이터 형식이다. 리스트와 마찬가지로 원소가 될 수 있는 값의 유형에는 제한이 없으며, 각각의 원소는 0부터 시작하는 정수 인덱스로 참조할 수 있다. 튜플은 소괄호((  )) 로 둘러싸인 콤마로 구분된 값으로 정의되는 것이 정석이다. 하지만 몇 가지 예외적인 방법으로 정의하는 것도 가능하다.

a = (1, "A")  # 튜플 a는 정수 1과 문자열 "A"의 쌍으로 묶여 있다. 

## 바인딩 구문의 우변에 위치하는 경우 컴마를 생략할 수 있다.
b = 2, True, None  

이렇게 만들어진 튜플의 각 원소는 리스트와 같은 방식으로 원소 및 부분열을 참조할 수 있다.

a = (1, 2, 3, 4, 5)
a[1] ## -> 2
a[::2] ## -> (1, 3, 5)

리스트와 튜플의 가장 큰 차이점은 리스트는 가변적인 값의 집합인 반면, 튜플은 하나로 단단히 묶여진 값의 순서쌍이며 개별 원소를 추가/삭제/변형하는 것은 허용되지 않는다는 것이다.

a[1] = 20
## TypeError : 'tuple' object does not support item assignment

그러한 특성을 제외하면 튜플은 리스트와 거의 똑같이 사용할 수 있다. 튜플은 FOR 구문을 통해서 개별 원소를 순회할 수 있고, 리스트 축약의 베이스로도 사용될 수 있다.

튜플 분해하기

사실 튜플은 바인딩시에 분해하는 것이 가능하다. 바인딩 구문의 좌변에도 튜플 문법을 쓸 수 있고, 이는 우변에 있는 튜플의 각 원소에 매칭된다. 예를 들어 아래와 같은 식으로 특정한 튜플의 구성요소는 정수 인덱스가 아니라 각각의 위치에 맞는 값으로 분해된다.

a = (1, 2, 3)
x, y, z = a
## x->1, y->2, z->3

## 위 문법은 아래의 동작과 일치
x = a[0]
y = a[1]
z = a[2]

## 사실, 좌변이 튜플일 때, 우변은 리스트여도 됩니다.
x, y, *z = [1, 2, 3, 4, 5]
## x->1, y->2, z->[3,4,5]

이 문법은 튜플을 조금 더 고급지게 다룰 때 다시 이야기할테니 눈여겨봐두고 다음으로 넘어가자.

연속열은 처음이라

튜플과 리스트는 튜플에서는 원소를 추가/변경할 수 없다는 사실을 제외하면 이 두 타입의 행동은 매우 비슷하고, 실제로 대부분의 파이썬 코드에서 튜플과 리스트는 서로 혼용해서 쓸 수 있다. 이것이 가능한 이유는 튜플과 리스트의 구조가 매우 닮아있기 때문인데, 바로 각각의 원소가 자신의 순서를 가진채로 줄을 지어 있는 연속열이기 때문이다. 파이썬에서는 리스트와 튜플외에도 이러한 연속열이  또 하나 있는데, 바로 문자열이다.

파이썬에서는 이러한 연속열이라고 묶을 수 있는 타입들을 실제로 연속열(Sequence)이라고 부르면서 거의 같은 것으로 취급한다. 세 연속열의 차이점은 이러하다.

  • 리스트는 어떤 값이든 원소로 가질 수 있으며, 변경이 가능하다.
  • 튜플은 어떤 값이든 원소로 가질 수 있으나, 변경이 불가능하다.
  • 문자열은 낱개의 문자만을 원소로 가질 수 있으며, 변경은 불가능하다.

이러한 차이를 제외하고, 즉 안에 들어있는 낱개의 원소 타입에 특화된 연산을 하거나, 연속열 자체를 변경하려는 시도를 하지 않는다면 리스트와 튜플 그리고 문자열은 마치 같은 타입처럼 행동할 수 있다. 즉 리스트 축약의 베이스이거나 FOR 문에서 순회할 집합으로 사용할 수 있다.

## 리스트를 이용한 리스트 축약
[ x * 2 for x in [1,2,3]]
#=> [2,4,6]

## 튜플을 그대로 사용할 수 있다.
[x*2 for x in (1, 2, 3)]
#=> [2,4,6]

## 문자열을 써도 된다. 단, (* 2)한 결과는 다르다.
[x*2 for x in "123"]
#-> ["11", "22", "33"]

연속열들의 공통적인 특징은 다음과 같은 것들이 있다.

  • + 연산자를 사용하면 두 연속열을 붙일 수 있다. (이 때 두 연속열을 같은 타입이어야 한다.)
  • * 연산자로 정수를 곱해서 연속열을 반복, 확장할 수 있다.
  • in 연산자를 사용한 멤버십 테스트가 가능하다.
  • seq[i] 와 같이 정수 인덱스를 통해서 개별 원소를 액세스할 수 있다. 인덱스가 음수인 경우에 뒤쪽에서부터 액세스한다.
  • seq[start:end], seq[start:end:step]의 문법으로 슬라이싱할 수 있다.
  • 바인딩 구문에 사용될 때, 좌변에서 튜플 문법을 써서 분해하는 것이 가능하다.

반복가능한 건 또 처음이라

연속열들은 공통적으로 FOR구문에 사용될 수 있다는 특징을 가지고 있다. (그리고 동시에 축약(comprehension)구문에도 사용할 수 있다.) 파이썬에는 비록 연속열은 아니지만 반복가능한 몇 가지 타입들이 더 존재한다. 아직 자세히 살펴보지 않은 그룹형식인 사전과 세트(set)가 그러하다. 그 외에도 지금까지 자주 등장했던 녀석이 하나 있는데, FOR문에서 예시로 많이 쓰인 range() 함수가 그러하다.

range()함수의 도움말을 읽어보면1 다음과 같은 내용을 확인할 수 있다. (아래 예시는 iPython을 이용해서 확인한 내용이다.)

In [1]: range?
Init signature: range(self, /, *args, **kwargs)
Docstring:
range(stop) -> range object
range(start, stop[, step]) -> range object

Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).
Type:           type

range()함수는 리스트나 튜플이 아닌 range 객체라는 것을 반환하고, 이 객체는 정수의 연속열을 생성할 수 있는 객체라고 설명하고 있다. 사실 파이썬에서는 이러한 반복가능한 특성을 가진 객체가 은근히 많고, 또 뒤에서 중요하게 다뤄질 제너레이터라는 개념을 배우고 나면 간단하고 편리하게 이러한 반복가능한 값을 만들어서 사용할 수 있다. 이에 대한 자세한 내용은 제너레이터에 대한 이해를 필요로 하니 다음으로 미루기로 하자.

다만 여기에서 반복가능 객체들은 마술 상자 같은 곳에서 정해진 순서에 따라서 값을 뿅뿅 만들어서 하나씩 내놓을 수 있는 능력을 가진 값들이라고 보면 된다. 다만 리스트, 튜플, 문자열은 그 때 그 때 마다 필요한 값을 만드는 것이 아니라, 모든 값을 한 번에 다 만들어서 펼쳐놓고 앞에서부터 하나씩 골라서 쓰는 개념으로 이해하고 있으면 되겠다.

연재 초반에 리스트가 파이썬에서 매우 중요한 타입이라고 언급했었는데 조금씩 그 중요성에 대한 감이 잡히는지 모르겠다. 리스트는 단일 값이 아닌 집합으로서 기능하면서, 연속열과 반복가능이라는 특성으로 이어지면서 FOR문과 그외 다른 유사한 타입들과의 성질을 많이 공유하고 있다. 따라서 이에 대해 이해하면 덤으로 그외 여러 타입들에 대해서 쉽게 친숙해 질 수 있고, 그러한 과정에서 지금은 말로 표현하기 어려운 어떤 언어 자체의 디자인 방향성 같은 것을 체득하게 될 것이다.

그러면 여기에 탄력을 더해서 리스트에 대해서 조금 더 많이 알아보는 시간을 가져야 할 것이다. 그러기 전에 파이썬의 내장 함수 몇 가지를 더 보고 진행해나가도록 하자.


  1. 파이썬 대화형 쉘모드에서나 help(이름)이라고 입력해서 도움말을 볼 수 있다. ipython을 사용한다면 이름? 이라고 입력하는 것으로 더 쉽게 확인이 가능하다.