(연재) 파이썬은 처음이라 – 리스트는 처음이라

지난 시간에 파이썬의 기본적인 값 타입에 대해서 살펴보았는데, 그 때 소개했던 리스트에 대해서 조금 더 자세히 알아보도록 하겠다. 리스트를 엄밀하게 정의하면 “0개 이상의 값의 원소들의 순서있는 집합“이라고 정의할 수 있다. 즉 일종의 집합(collection)이나 컨테이너(container)의 형태로 그 내부에 여러 개의 값을 원소로 가질 수 있으며 다른 언어에서는 배열(Array) 혹은 벡터(Vector)라 불리는 타입과 비슷하다.

리스트는 순서있는 집합이므로 각 원소는 리스트 내부에서 고유한 순서를 가지고 있는데, 이 순서를 인덱스라고 한다. 인덱스는 0부터 시작하는 정수로, 첫번째 원소가 0번 인덱스를 가지며, 길이가 n 인 리스트의 마지막 원소의 인덱스는 n – 1 이다. 리스트의 원소가 될 수 있는 타입의 종류에는 제한이 없으며, C의 배열과는 달리 서로 다른 타입의 원소들이 같은 리스트에 함께 포함될 수도 있다.

리스트를 만드는 방법

리스트는 수학에서의 ‘집합’과 비슷한 개념의 컨테이너이다. 수학에서의 집합은 두 가지 표기법에 의해서 표현되는데 각각의 원소를 명시해서 표현하는 원소나열법과 집합 내의 각 원소들이 갖추는 조건만을 명시해서 정의하는 조건제시법이 있다. 파이썬에서 리스트를 표현하는 방법도 이와 비슷하게 두 가지가 있다. 하나는 원소 나열법에 대응될 수 있는 리스트 리터럴(list literal)이며, 다른 하나는 조건 제시법에 대응하는 리스트 축약(list comprehension) 문법이다.

리스트 리터럴

리스트 리터럴은 대괄호([   ])속에 각각의 원소가 되는 값이나, 이름을 넣고 각 원소를 컴마로 구분하는 표현이다. 각각의 원소의 타입에는 제한이 없다.

리스트리터럴 ::= [ 원소값표현식,... ]

다음은 리스트 리터럴을 사용해서 정의하는 몇 가지 리스트의 예이다.

## 정수
numbers = [3, 7, 2, 9, 8]
## 문자열
fruits = ['apple', 'banana', 'orange', 'kiwi']
## 불리언
tof = [True, True, False, False, True]
## 혼합 - 여러 타입들을 한 리스트에 같이 담을 수 있다.
aList = [2, 3.14, 'hello', None, True]
## 이중 리스트 - 리스트 역시 '값' 타입이므로 리스트의 원소가 될 수 있다.
nested = [[1,2,3], ['appel', 'orange'], [None, True]]

list 생성함수

지난 시간에 키보드로 정수값을 입력받는 방법을 소개하였는데, 이 때 int() 함수가 언급되었다. 이 함수는 “정수로 바꿀 수 있는 무언가를 정수로 바꿔주는 함수”라고 하였다. 이와 비슷하게 list()라는 함수가 있다. 이 함수는 “리스트로 바꿀 수 있는 무언가”를 리스트로 바꾸어주는 함수이다. 그렇다면 무엇이 리스트로 바꿔질 수 있을까? 바로 “반복가능한” 객체이다. 반복가능한 객체에는 리스트, range 객체1, 문자열2, 튜플, 집합 등등이 있는데, 이러한 객체를 리스트로 만들 수 있다.

x = list(range(10))
## [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

y = list("apple")
## ["a", "p", "p", "l", "e"]

리스트 축약

리스트 축약은 반복자(iterator)를 통해서 리스트를 생성하는 것으로 for 문을 한줄로 쓴 것과 비슷하게 표현된다. 패턴은 다음과 같다.

[ 표현식 for 식별자 in 반복자 ] [ 표현식 for 식별자 in 반복자 if 조건식]
  • 식별자 : for 문에서 반복되는 원소의 이름으로 쓰이는 변수
  • 반복자 : 리스트, 문자열, range 객체 등 for 문에 쓸 수 있는 반복가능한 객체
  • 표현식 : for 문의 반복되는 값을 사용한 표현식으로, 매 반복에 대한 리스트의 원소를 만드는 식이다.

리스트 축약을 쓰기 위해서는 각 원소들의 베이스가 될 다른 리스트나 반복자가 필요하다. 여기서 반복자란 지난 강좌중 ‘문법은 처음이라 예제편’에서 소개된 for 문에서 사용되는 반복가능한 값을 의미한다. 반복가능한 값의 각 요소값에 대해서 주어진 식별자로 이름을 붙이고, 그 이름을 사용하는 표현식으로 각 원소를 만든다. 예를 들어 range(10)은 0부터 10개의 정수를 생성할 수 있는 수열이므로, 1에서 10까지의 정수로 구성된 리스트는 range(10)의 각 값에 1을 더해서 만들 수 있다. 따라서 [1, 2, 3, … , 10] 의 리스트는 다음과 같이 리스트 축약으로 표현할 수 있다.

[ x + 1 for x in range(10) ] 
## range(10) -> 베이스가 되는 반복자로 0, 1, 2, ..., 9
## x  -> 베이스가 되는 반복자의 각 원소에 붙이는 이름
## x + 1 -> 각 원소에 대한 표현식
## => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

표현식을 어떻게 작성하느냐에 따라서 다양하게 원하는 리스트를 만들 수 있다.  리스트 축약의 중요한 점은 리스트 혹은 리스트에 준하는 어떤 반복가능한 시퀀스의 각 원소를 표현식을 통해서 변형한 새로운 리스트를 만드는 방법이라는 점이다. 즉 리스트 축약은 그 자체로 리스트를 구성하는 문법인 동시에, (앞의 강좌에서 언급한 바와 같이) 리스트나 반복가능한 시퀀스를 하나의 값으로 보고 그 리스트를 다른 리스트로 변화시키는 연산으로도 볼 수 있다.

## 1~10의 제곱수
[ (x + 1)** 2 for x in range(10) ]
#= [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

## 어떤 리스트를 3으로 나눈 나머지들
[ x % 3 for x in [3, 8, 9, 13, 53, 89]]

## 대문자로된 단어들
[ w.upper() for w in ['apple', 'banana', 'orange']]
#= ['APPLE', 'BANANA', 'ORANGE']

리스트 축약의 두 번째 문법은 일종의 확장 문법인데, 기본 리스트 축약 뒤에 IF 절이 붙어 있는 모양으로 IF 절에는 또 하나의 표현식이 들어간다. 이 조건절의 표현식이 참으로 평가될 때만 해당 원소가 결과에 포함될 수 있다. 즉 IF 절을 이용해서 특정 조건에 맞지 않는 원소를 걸러낼 수 있다. 예를 들어서 20보다 작은 3의 배수를 만들기 위해서는 두 가지 방법이 존재할 수 있다.

  1.  0 < n < 7 의 범위의 자연수 n 에 대해서 n * 3 하는 방법
  2.  0 < n < 20 의 범위에 자연수 n에 대해서 n이 3의 배수인 것만 골라내는 방법

첫번째 방법이 더 간단해 보이지만, 범위를 정확히 산정하기 위해서 다시 계산을 해야한다는 점이 조금 불편하고, 코드상으로 보았을 때 “20보다 작은 3의 배수”를 찾는다는 의도가 덜 명확할 수 있다. (물론 루프를 19번 도는 것보단 6번 도는 것이 더 빠를 수는 있다.) 사실상, 리스트 축약이 조건제시법에 대응하는 방법이라는 점에서 개인적으로는 2번의 방법을 선호하는 편이다.

# 조건 제시법으로 표현하는 20 미만의 3의 배수에 1을 더한 값
{ x | 0 < x < 20, x는 3의 배수 }

# 리스트 축약
[ x for x in range(1, 20) if x % 3 is 0 ]

리스트 축약의 의미

리스트 축약은 베이스가 되는 어떤 리스트 A의 각 원소를 1) 특정한 조건식으로 필터링하고 2) 표현식을 통해서 변형한 값을 원소로 갖는 새로운 리스트를 만들어내는 과정을 수반한다. 앞에서도 말했지만, 이는 결국 리스트 그 자체를 값으로 보고 각 원소에 표현식을 적용하여, “변형된 새로운 리스트를 만드는” 과정이라고도 볼 수 있다. 이 때 사용되는 표현식은 사실 변수를 받아서 평가하는 일종의 “값 변형 장치”로 볼 수 있다. 이 관점에서 다음 상황을 살펴보자.

  1. 어떤 정수 리스트 A가 있다고 하자.
  2. 그리고 어떤 정수 n을 2배 한 후에 3을 더하는 함수 f(n) = n * 2 + 3이 있다고 하자.

함수 f(n)은 정수를 인자로 받아서 정수를 반환하는 함수이다. 우리는 “정수 리스트를 2배 한 후에 3을 더하는” 연산을 어떻게 수행해야 하는지 모른다. 정의된 바가 없기 때문이다.3 사실, 정수 리스트에서 정수를 더하는 연산을 상상하기도 어렵다. 대신에 다음과 같이 함수 f(n)이 리스트 A 속으로 들어가서 A의 모든 원소에 대해 동시에 적용되는 작용에 대해서는 상상해볼 수 있다.

함수 f                   |  리스트 A  |  리스트 B
f(n) = n * 2 + 3 ---->    [ 1,     -|-> [ 3,
                            2,     -|->   7,
                            3]     -|->   10]

어떤 함수를 어떤 컨테이너 속으로 들여보내서, 컨테이너의 각 원소에 동일하게 적용하게 하는 것을 사상(mapping)이라고 한다. 어차피 사상에 대해서는 고등학교 정규 교과에서 다루는 것도 아니고, 우리가 하스켈을 배우는 것도 아니니 자세하게 파고 들 생각은 없지만, 이 개념은 매우 중요하다. 왜냐하면 이전까지 대부분의 프로그래밍 강좌에서 배열이나 리스트는 한 단위의 값이 아니라, “자료 구조”로서 취급되었고, 따라서 여기에 값을 넣고 빼고, 특정위치에 삽입하고 하는 조작이 중요시되었다. 하지만 내가 생각할 때 진정으로 중요한 것은 자료 구조 그자체를 어떻게 조작하느냐가 아니라 입력값과 출력값이 어떤 관계를 가지고 있느냐는 것을 생각하고 그것을 표현식으로 표현하는 방법을 익히는 것이다. 문제를 보는 관점을 조금만 거시적으로 옮겨보면 전체적인 흐름이 보이고, 그것을 간략하게 정리할 수 있게 되면 코드가 단순해진다. 코드가 단순해진다는 것은 그만큼 문제를 단순화해서 풀 수 있다는 것이고 다시, 그만큼 명료하게 사고할 수 있게 된다는 것을 말한다.

리스트의 원소

다시 리스트의 원소로 돌아가보자. 리스트는 원소들의 순서가 있는 집합이라고 했다. 각 원소는 리스트 내에서 몇 번째에 위치한다는 순서를 가지고 있으며, 이를 인덱스라고 한다. 모든 원소는 고유의 인덱스를 가지고 있으니, 인덱스를 이용하면 리스트의 여러 원소 중에서 하나의 원소를 정확하게 가리킬 수 있다. 즉 리스트 내의 특정한 원소를 액세스하기 위해서는 인덱스를 사용하면 된다는 말이다.

리스트의 인덱스는 0부터 시작하는 정수이며, 리스트의 각 원소는 그 인덱스를 i 라 할 때 aList[i]와 같은 식으로 읽을 수 있으며, 쓸 수도 있다. 읽는다는 말은 aList[i] 라는 표현식이 리스트 내의 i 번째 원소로 평가된다는 말이며, 쓴다는 말은 aList[i] = 3 과 같이 리스트의 i 번째 원소를 3이라는 값으로 교체한다는 말이다.

aList = [1, 2, 3, 4]
##       ^  ^  ^  ^ 3번 원소
##       |  |  └ 2번 원소
##       |  └ 1번 원소
##       └ 0번 원소    

a[0] #= 1
a[1] #= 2
a[2] = 30 ## 2번 원소를 30으로 교체했다. 
# A = [1, 2, 30, 4] 
#            ^^ 교체됨

만약 aList[4]와 같이 범위 밖의 인덱스를 참조하려고 하면 IndexError가 발생하면서 프로그램의 실행이 중단된다. 그럼 aList[-1]로 접근하게 되면 이 경우도 범위를 벗어나서 에러가 될 것인가?

음의 인덱스

인덱스가 음수인 경우, 뒤에서부터 몇 번째 원소인지를 센다. 이 때 -1은 리스트의 맨 마지막 원소이며 (-0 은 결국 0이니 리스트의 맨 첫 원소가 될 것이다.) 거꾸로 -2, -3, -4…와 같이 참조할 수 있다. 여기서도 원소가 4개인 aList에 대해서 aList[-5]와 같이 존재하지 않는 인덱스를 참조하려하면 IndexError가 발생한다.

aList = [2,3,4,5]
aList[-1] ## 5
aList[-2] ## 4

슬라이스 – 부분집합에 대한 참조

리스트에 대해서 하나의 원소를 참조하는 것외에 부분집합을 참조해야 하는 경우가 있다. 예를 들어서 0~9의 10개의 정수 리스트를 만들고 첫 5개 원소를 갖는 부분집합과 나중 5개 원소를 갖는 부분집합을 나눠서 참조할 필요가 있을 것이다. 이렇게 리스트로부터 부분열을 참조할 때 사용하는 것이 슬라이스 문법인데, 인덱스를 통한 원소 참조 방법을 약간 확장한 것이다. 4

슬라이스 문법을 정리하면 다음과 같다.

  • 슬라이스는 숫자와 콜론을 구분자로 사용하여 표현한다.
  • 전체형태는 시작:끝:간격 이다.
  • 시작인덱스 위치부터 끝 인덱스까지, 주어진 간격만큼 건너뛴 원소를 선택한다. 이 때 끝 인덱스는 범위에 포함되지 않는다.
  • 간격은 생략할 수 있고, 생략하는 경우 1이다.
  • 끝은 생략할 수 있고 생략하는 경우 리스트의 길이, 즉 마지막 인덱스이다.
  • 끝이 있는 경우 시작을 생략할 수 있고 이 경우 0이 된다.

리스트 aList = [1,2,3,...,10] 이라고 할 때, 다음과 같은 문법으로 부분 집합을 구할 수 있다. 참고로 이 문법의 의미는 앞서 이전 시간에 소개한 range() 함수의 사용방법과 매우 유사하니 같은 맥락에서 이해하면 쉽다. 아래는 몇 가지 각 경우에 대한 예제이다.

  • aList[3] : 3번 인덱스(4번째 원소, 4이다.)에 대한 단일 원소를 참조한다.
  • aList[0:4] : 콜론으로 구분되는 두 개의 숫자가 쓰이면 start:end 의 의미가 되며, 이 때 range(0, 4)와 같이 end 값은 범위에 포함되지 않는다. 따라서 aList[0:4][1, 2, 3, 4]으로 3번 인덱스까지만 포함한다.
  • aList[4:8] : 같은 의미에서 4번, 6번, 7번 인덱스를 포함한다. 즉 [5, 6, 7]에 해당한다.
  • aList[2:10] : aList[10]은 존재하지 않는 인덱스이기 때문에 참조할 수 없다. 하지만 aList[2:10]에서 10은 끝 값이며 실제로는 참조하는 범위에 포함되지 않는다. 따라서 2번 인덱스부터 끝까지라는 의미이며, 에러가 아니다. 같은 의미로 aList[2:12]도 안전하게 동작한다.
  • aList[:5]  : 시작값을 생략해버리면 리스트의 처음부터라는 의미이다. 이는 [1,2,3,4,5] 에 해당한다.
  • aList[5:] : 끝값을 생략해버리면 리스트의 끝까지라는 의미이다. 이는 [6, 7, 8, 9, 10]에 해당한다.
  • aList[0:8:2] : 세 개의 값을 사용하면 이는 start:end:step의 의미이다. aList[0:8:2][1, 3, 5, 7] 을 의미한다. (인덱스로는 0, 2, 4, 6까지 사용되며 stop에 해당하는 8은 역시 포함되지 않는다.)
  • aList[:8:2] : 첫 값을 생략할 수 있으므로 위 표현을 이렇게 쓸 수 있다.
  • aList[5::2] : end 값을 생략할 수 있고, 5번 인덱스부터 한 칸씩 건너뛰면서 선택한다.
  • aList[::2] : 간격이 명시되면 start, end 값은 동시에 생략될 수 있다. [1, 3, 5, 7, 9]를 구하게 된다.
  • aList[:] : step 까지 쓰지 않으면 [:] 으로 참조할 수 있다. 이는 리스트의 처음과 끝까지를 의미하므로 전체 리스트를 의미한다. 이렇게되면 아무 의미없는 문법인 것 같지만, 이것은 리스트 전체에 대한 사본을 얻는 셈이 된다.
  • aList[-1:-5:-1], aList[8:3:-1] : 시작 인덱스가 끝 인덱스보다 뒤에 있고, 간격값이 음수인 경우에는 뒤쪽에서부터 앞으로 원소를 참조한 부분집합이 만들어진다. 즉 step이 음수이면 만들어지는 결과는 뒤에서부터 추출하여 역순이 된다.
  • aList[::-1] : 리스트 전체를 역순으로 참조했다. 즉 전체 리스트를 뒤집은 사본이다.

인덱스에 대한 이해

많은 초보자들이 슬라이싱에 대해서 헷갈려하거나 실수를 많이하게 된다. 이것은 인덱스를 단순히 i 번째 원소를 가리키는 값으로 이해하기 때문에 빚어지는 문제이다. 사실 인덱스는 특정한 원소가 아닌, “그 원소의 바로 앞쪽”을 가리키는 숫자이다. 그림으로 표현하면 아래와 같다.

리스트 A가 [1, 2, 3, 4, 5]라면  A[0]은 실질적으로 “인덱스 0으로부터 1칸의 값”을 의미한다. 따라서 1이 되는 것이다. A[5]는 인덱스 5로부터 1칸을 의미하는데 인덱스 5 뒤에는 값이 없기 때문에 IndexError가 나게 된다.  그림에서 음수 인덱스는 아래쪽에 적어 두었다. 역시 같은 원리로 a[-1]이라고 하면 -1의 위치에서 오른쪽 한 칸인 셈이다.

이 방식으로 인덱스를 이해하면 슬라이스에 대해서도 보다 직관적으로 이해된다. A[2:4]를 구하려면 인덱스 2와 4 사이의 값이다. 위 그림에서 인덱스 2와 4 사이에는 3, 4 가 있다.  또한 2, 3 을 음수 인덱스로 슬라이스하기 위해서는 A[-4:-2]가 되어야 함을 알 수 있다.

자 그렇다면 a[2:2]는 무엇을 의미할까? 한 번 고민해보도록 하자.

리스트의 가변성

지금까지 접해왔던 다른 값 유형과 달리 리스트는 가변적(mutable)이다. 무슨 말인고 하니, 1 이라는 정수값이 있다고 하자. 이 정수값은 다른 값으로 바뀌지 않는다. 아니, 이게 무슨 말이야? 만약 이 글을 읽는 당신이 C를 첫 언어로 시작해서 파이썬으로 건너온 프로그래밍 유경험자라면 동공이 흔들리기 시작할지도 모르겠다. 아래의 코드를 보자.

a = 1
a = a + 1
# a => 2

C에서는 이 코드가 이렇게 해석된다. (물론 문법은 파이썬으로 간주하고…)

  1.  a라는 변수의 메모리에 1이라는 값을 쓴다.
  2. a라는 변수의 메모리에 a의 값(1)에 1을 더한 값을 쓴다. 즉 1이라는 a의 값이 2로 바뀌었다.

그런데 파이썬에서는 그렇지 않다. 기본 타입 중에서 컨테이너가 아닌 모든 타입은 불변하다. 1 + 1 = 2 인데 왜 불변인가? 그것은 파이썬에 1이라는 값에 1을 더한다는 것은, 1 + 1 이라는 표현식을 평가한다는 말이다. 그 결과는 2라는 1하고는 아예 다른 값이다. 그말인 즉슨 1이라는 정수가 그 속에서 뭔가 꿈틀거리다가 2라는 값으로 변신하는 것이 아니라, 1이라는 정수가 있고, 2라는 정수가 그냥 따로 있을 뿐이다. 파이썬에서 위 코드의 올바른 해석은 다음과 같아야 한다.

  1. a라는 이름을 정수 1 에 붙인다.
  2. a + 1 을 평가한다. 이 값은 2라는 다른 정수값이다.
  3. 이 2 라는 정수값에 a라는 이름을 붙인다. 정수 1에는 더 이상 a라는 이름이 붙지 않는다.

그래서 파이썬에는 대입이라는 개념이 없다고 하는 것이다. a에 1이 대입되었다 (라고 설명하면 많은 프로그래밍 교재는 그릇이나 상자에 1이라는 값이 들어가는 것처럼 묘사한다.)라고 생각하면 간단한 수식이나 표현조차 너무 어렵고 이해할 수 없는 것처럼 동작하는 경우가 종종있을 것이다. 어쨌든 중요한 것은 정수라는 타입은 연산을 통해서 다른 정수값이나 실수값을 데려올 수는 있지만, 그 자신이 변신하지는 않는다는 것이다. 그런데 리스트의 경우에는 조금 사정이 다르다. 하나의 리스트는 이름표를 옮기는 변경 없이 리스트 내부의 원소값이 변할 수 있다.

a = [1, 2, 3]
a[0] = 10

리스트의 내용을 변경하는 동작에는 다음과 같은 것이 있을 수 있다.

  1. 특정 인덱스의 값을 다른 값으로 교체
  2. 특정 인덱스의 값을 제거
  3. 리스트의 끝, 혹은 특정한 위치에 인덱스를 삽입
  4. 리스트의 부분집합을 다른 집합으로 변경 (리스트 슬라이스를 다른 리스트로 교체

리스트 연산

리스트에 대한 기본적인 연산은 다음과

  • 원소/부분열 액세스 : aList[3], aList[3:5] 등과 같이 인덱스를 통해서 특정 원소를 읽거나, 변경할 수 있다.
  • 연결 : aList + otherList 와 같이 + 연산자를 통해서 두 개의 리스트를 이어 붙일 수 있다. 반대로 - 연산은 지원하지 않는다.
  • 반복 : aList * 정수의 형태로 주어진 리스트를 여러 차례 반복하는 리스트를 생성할 수 있다. 역시 / 연산은 지원하지 않는다.
  • 멤버십 연산 : e in aList 의 형태로 in 을 원소와 리스트 사이의 연산자로 쓰면 원소 eaList내에 들어있는지를 확인하는 연산이 된다.

리스트를 다룰 때에는 그외의 다른 함수나 리스트의 메소드들을 사용하는 방법들이 더 있지만, 이러한 내용은 별도 토픽으로 다루도록 하겠다.

 


  1. range() 함수를 실행한 결과를 print()해보면 실제로 수열의 내용이 표시되는게 아니라 그냥 range(0, 10) 이라고 표시된다. 
  2. 문자열은 각각 낱개의 글자가 연속적으로 모여있는 집합과 비슷한 개념이다. 
  3. 그런데 재미있게도 파이썬에서는 리스트는 양의 정수와 곱할 수는 있다. 리스트 * n 하면 리스트를 n번 반복한 리스트가 만들어진다. 
  4. 사실 엄밀하게 말하면 인덱스에 의한 원소 참조가 슬라이스의 특별한 한 경우라 할 수 있다. 

(연재) 파이썬은 처음이라 – 문법은 처음이라 : 예제편

지난 번 문법에 대해 설명하면서, 각 문법의 패턴에 한정해서 설명하면서 코드 소개를 가능한 피했었다. 이번 글에서는 각 구문별 코드의 예제와 각각의 코드가 어떤식으로 실행되는지를 설명하는 시간을 갖도록 하겠다. 또 이 작성하는 예제코드에는 몇 가지 기본 입출력 관련 함수가 등장한다. 내장 함수에 대한 토픽을 따로 마련할 생각이지만, 워낙 기본적인 함수나 메소드들은 진도에 무관하게 그 때 그 때 소개하도록 하겠다. 먼저 기본적인 입출력 함수와 더불어서 간단한 바인딩 구문을 소개해보겠다.

먼저 두 개의 함수를 소개한다. input, print 라는 이름의 함수이다. 함수에 대한 자세한 설명은 일단 미루도록 하고, 파이썬에서 함수는 어떤 입력값을 받아서 내부에서 처리한 출력값을 반환하는 장치를 말한다. 파이썬 문법에서 함수는 그 이름 뒤에 괄호를 붙여서 표현하며, 인자값(arguments)을 받는 함수는 그 괄호안에 인자를 넣어서 호출한다.  input 함수는 인자 없이도 호출이 가능한 함수인데, 사용자로부터 입력된 한줄의 문자열을 리턴한다. 즉 input() 이라고 표현하면, 이 또한 표현식이며, 그 표현식은 사용자가 키보드를 두드려서 엔터까지 친, 사용자로부터 입력받은 문자열로 평가된다. input() 함수가 가장 기본적인 입력 함수라 한다면, 출력을 위해서는 print()라는 함수가 있다. print() 함수는 인자로 전달된 값을 문자열로 바꿔서 그 내용을 화면에 출력한다. print() 함수는 입력값이 뭐가되었든, 그 내용을 출력하고 표현식 자체는 None으로 평가된다.

 

name = input() #1
print(name) #2

이 두 라인의 간단한 코드는 하나의 완전한 프로그램이며 각각의 라인은 다음과 같이 해석된다.

  1. 첫 번째 라인의 구문 구조는 바인딩 구문(binding statement)이며, input() 함수의 결과값에 name이라는 이름을 붙인다.
  2. name의 값을 출력하기 위해 print 함수에게 name을 인자로 전달한다.

위 소스 코드를 print_name.py 라는 이름으로 저장하고 실행해보자. 프로그램을 실행하면 처음에 아무일도 일어나지 않는다. 단지 커서가 깜빡거릴 뿐이다. 하지만 우리는 이 때 콘솔에 키보드로 어떤 문자열을 입력할 수 있다. 아무 글자들이나 입력한 후에 엔터키를 누르면 방금 입력했던 내용이 그대로 출력된다.

첫번째 라인에서 input() 함수를 평가하는데에는 제법 긴 시간이 걸린다. 왜냐하면 이 함수는 “우리가 키보드로 입력한 값”으로 평가되어야 하기 때문에 실제로 엔터키를 입력하는 시점까지는 평가를 지연하고 기다리게 된다. 무엇이되었든 글자들을 입력하고 엔터키를 치게되면 다음과 같은 일들이 벌어진다.

  1. 바인딩 구문은 우변이 먼저 평가된다. input() 함수가 평가되면서 우리가 입력한 문자열 값이 된다.
  2. 좌변으로 바인딩되면서 name 이라는 이름이 그 문자열을 가리키게 된다.

어떤 이름이 값을 “가리킨다” 혹은 “참조한다”는 표현이 너무 추상적이고 어렵다면, 어떤 종이 상자에 문자열을 집어넣고 거기에 name 이라고 쓰여진 이름표를 붙여두었다고 생각하면 된다. (실제로 이 비유는 파이썬의 바인딩에 대한 엄청나게 적절한 비유이다.)

두번째 라인을 보자. 두 번째 라인은 그냥 표현식 하나가 달랑 쓰여 있다. print()라는 함수를 평가하는데, 괄호 안에 name 이라는 이름을 집어 넣었다. print() 함수는 “주어진 값을 문자열로 변환해서 화면에 출력한다”고 했다. 사실 값을 받는 것이 아니라, 값의 이름표를 받는다고 생각하면 된다. print() 함수의 내부에서는 name 이라는 이름표를 가지고, 좀 전에 문자열을 담아두었던 상자를 찾고, 그 내용물을 화면으로 출력한다.

이 두라인이 연속적으로 평가된, 최종 결과는 None 이다. 이 프로그램은 어떤 값도 만들어내지 않았지만, 프로그램의 외부 세계에 대해서는 “어떤 일”을 하였다. 프로그램 외부의 시선으로 바라봤을 때, 이 두줄 짜리 프로그램은 다음과 같은 일을 했다.

키보드를 두드린다 ==> { 마법같은 프로그램 } ==> 화면에 출력된다.

바인딩 구문을 사용하는 몇 가지 예를 더 보도록 하자.

x = 51
y = 43
z = (x + y) / 2  
print(z) 

프로그램을 실행하면 47.0이 출력된다.  첫 줄부터 세번째줄까지 3개 라인은 각각 바인딩 구문이다. 51 이라는 정수값에 x라는 이름을, 47이라는 정수값에 y라는 이름을 붙인 후, (x + y) / 2 라는 표현식을 평가하고 그 값을 z라는 이름에 바인딩했다. (그 결과는 47.0) 그리고 마지막 라인은 이 z를 출력하기 위해 print 함수를 호출했다.

정수를 입력받기

키보드를 통해서 입력 받은 숫자를 더하여 출력하는 프로그램을 작성해보자. 바로 위에서 input과 print 함수를 소개했고, 정수값은 더하기 연산을 할 수 있으니 간단하게 만들 수 있을 것 같다. 다음과 같이 말이다.

a = input()
b = input()
c = a + b
print(c)

뭔가 좀 이상하다. 프로그램을 실행하고 처음에 1을 입력하고 엔터, 그리고 2를 입력하고 엔터를 하면 3이 나와야 할 것 같지만 실제로는 12가 출력된다. 어디가 잘못되었을까? 바로 input()을 평가한 값의 타입을 잊었었던 것이다.

input 함수는 문자열을 리턴한다고 했다. 즉 input()의 표현식은 문자열타입 값으로 평가된다. 결국 1, 2를 각각 입력했다면 c = a + b 라는 구문은 c = "1" + "2" 이므로 "12"로 평가되고 그것이 출력된 것이다. 따라서 입력된 숫자를 가지고 정수값을 만들어줄 어떤 마법이 필요하다. 이 때 사용하는 마법은 바로 int() 함수이다.1 int() 함수는 정수로 바뀔 수 있는 어떤 것을 넣어주면 그것을 정수로 바꾼 값을 내놓는 함수이다.2  여기서 우리가 입력하는 글자들이 모두 숫자라면 실제로 정수를 만들어준다. 만약 “apple”, “banana”와 같은 단어를 입력하면? 글쎄 나는 그것을 정수로 바꾸는 방법을 모른다. 아마 파이썬 해석기도 그 방법을 모를 것이다. 숫자로 해석될 수 없는 문자를 집어넣으면 ValueError가 났다면서 프로그램이 중단될 것이다.

a = input()
x = int(a)
b = input()
y = int(b)
z = x + y
print(z)

이번에는 아주 잘 동작한다. 그런데 잠깐 여기를 보자. input()이라는 표현식은 문자열이라고 했다. 여기서는 그 값을 a라는 이름에 붙인 후 곧바로 다시 int()함수에 인자로 전달했다. 즉 함수의 인자로 전달 되는 것은 값이므로 이름 대신에 표현식을 그대로 쓸 수 있다. 즉, int(input()) 이라고 쓸 수 있다는 것이다. 같은 방식으로 z라는 이름을 보면, 이 이름은 중간 계산 결과를 가리키는 이름이다. 이 경우 z를 원래의 a + b로 치환하여  print(a + b) 라고도 쓸 수 있다.

a = int(input())
b = int(input())
print(a + b)

그렇다면, 다음과 같이도 쓸 수 있는 것 아닌가?

print(int(input()) + int(input())

맞다. 다만 이건 괄호가 너무 난무해서 약간 읽기가 힘들어진다. (사람에 따라서는 뭐 이정도는 쉬울지도 모르겠다.) 어떻게 쓰던지 모두 “정상적인” 코드이며 동작에 문제는 없다. 대신에 파이썬에서는 “간결하고 분명하고 읽기 좋은 것”이 가장 좋은 것이다. 어느쪽을 선호하든지 그것은 본인의 몫이다. 다만 첫번째 코드처럼, 축약할 수 있는 부분들까지 전부 무시하고 일일이 바인딩하고 쓰는 것은 그렇게 좋은 습관은 아니다. 그것이 매우 읽기 쉬운 것은 사실이나, 간단한 정보를 전달하기 위해서 너무 너저분하게 쓴다는 느낌을 준다.

그러면 어느 수준으로 축약하는 것이 좋을까? 그것은 “지금 어떤일을 하고 있는가”를 적절히 표현하는 수준에서 정하는 것이 맞다. 위 코드가 하는 일을 말로 표현하면 “두 개의 정수를 입력 받아, 그 합을 출력한다”이다. 따라서 두 개의 정수를 입력 받고, 더해서, 출력하는 수준을 코드로 표현하면 된다. 그리하면 세 번째의 세 줄로 표현된 코드가 가장 적당해 보인다.

IF문

IF문은 특정한 조건식에 따라서 프로그램의 진행 방향이 달라지는 분기문이라고 했다. 간단한 몇 가지 예를 들어보자. 다음은 입력한 값이 홀수인지 짝수인지를 판단하여 그 결과를 출력하는 프로그램이다.

n = int(input('숫자를 입력하세요')) #1
if n % 2 == 0: #2
  print("짝수입니다.") #3
else:
  print("홀수입니다.") #4

위 코드의 로직은 다음과 같다.

  1. 맨 처음에는 키보드로부터 수를 하나 입력받고, 계산을 위해서 정수로 변환했다.
  2. 짝수는 2로 나눈 나머지가 0인 수이다. 나머지 연산자를 써서 n % 2 를 구하고 그것이 0과 같은지 검사한다.
  3. 그 결과가 참이라면 “짝수입니다.”를 출력한다.
  4. 그 결과가 거짓이라면 “홀수입니다.”를 출력한다.

조건에 따라 분기한 후 다시 다른 조건으로 분기하는 경우도 생각할 수 있다. 예를 들어 2의 배수이면서 5의 배수인 수가 있다면 이 수는 2와 5의 공배수이며 즉 10의 배수가 된다. 이 과정을 표현해보자. 그리고 짝수가 아닌 경우에도 5의 배수가 된다면 이를 출력해보자.

n = int(input())
if n % 2 == 0:
  if n % 5 == 0:  #1
    print('10의 배수입니다.') #2
  print('짝수입니다.')  #3
elif n % 5 == 0:  #4
  print('5의 배수입니다'.) 
else: #5
  print('그냥 홀수입니다.')

위 코드가 실행되는 과정을 살펴보자.

  1. 먼저 n이 짝수로 판정된다면 다시 5의 배수인지를 검사받는다.
  2. 짝수이면서 5의 배수이므로 “10의 배수입니다”가 출력된다.
  3. 이 문장은 5의 배수 조건문의 블럭이 아니다. 따라서 10의 배수여부에 상관없이 짝수라면 항상 출력된다. 만약 n이 10의 배수라면 “10의 배수입니다.”와 “짝수입니다.”가 나란히 출력될 것이다.
  4. 짝수가 아니라면 5의 배수인지 다시 검사한다.
  5. 짝수도 아니고 5의 배수가 아니라면 “그냥 홀수입니다”가 출력된다.  4번에서 “5의 배수입니다”가 출력되었다면, 이 문장은 출력되지 않을 것이다.

ELIF 절을 사용해야 하는 가장 대표적인 문제는 주어진 점수에 대해서 점수 구간을 두고 평점을 계산하는 프로그램이다.

어떤 학교의 평점은 A, B, C, D, F로 나뉘어집니다. 이 평점은 시험 점수의 구간에 따라 구해집니다. 각 구간은 다음과 같습니다.

  • 50점 미만인 경우 F를 받습니다.
  • 50점 이상 65점 미만인 경우 D를 받습니다.
  • 65점 이상 75점 미만인 경우 C를 받습니다.
  • 75점 이상 85점 미만인 경우 B를 받습니다.
  • 85점 이상인 경우 A를 받습니다.

먼저 특정 구간에 대해 어떤 값이 있는지를 어떻게 검사하는지 살펴보자. “50점 이상 65점 미만”인 경우를 어떻게 체크할까?

  • 점수값을 N 이라 하자
  • “50점 이상”은 N >= 50 으로 표기할 수 있다. 혹은 방향을 바꿔서 50 <= N 으로 표기할 수 있다.
  • “65점 미만”은 N < 65로 표기할 수 있다.
  • 50점 이상, 65점 미만은 위의 두 조건을 동시에 만족해야 한다. 예를 들어 70점은 65점 미만의 조건을 만족하지 않기 때문에 이 범위에 들지 않는다.
  • 각각의 부등식은 참/거짓으로 판단되며, 두 부등식을 동시에 만족해야 하는 경우 and를 사용해서 결합한다.
  • 따라서 N >= 50 and N < 65 라고 쓸 수 있다.
  • 그런데, 파이썬의 부등식은 연쇄적으로 결합할 수 있다. 따라서 50 <= N < 65 로 쓰는 것 역시 허용된다.

그렇다면 다음과 같이 if, elif, else를 써서 코드를 작성할 수 있을 것이다.

n = int(input())
if n < 50:
  print("F")
elif 50 <= n < 65:
  print("D")
elif 65 <= n < 75:
  print("C")
elif 75 <= n < 85:
  print("B")
else:
  print("A")

다만, 하나의 구간에 대해서 50 <= n < 65를 보는 것은 맞는데, 이 로직을 따라가보면 우선 50점 미만인지 체크한 다음에 두 번째로 50이상 65미만 조건을 체크한다. 만약 N이 50 미만이었다면 앞의 IF 절에서 분기했기 때문에 50이상이라는 조건을 이미 만족하는 상태로 ELIF 절을 만난다. 따라서 각 ELIF 절의 맨 왼쪽 부등식은 사실은 항상 참이 되므로 필요없는 구문이된다. 즉 elif n < 65: 만 있어도 이 전체 로직은 문제없이 작동할 것이다.

WHILE 문

WHILE 문은 특정 조건을 만족하는 한 루프를 계속 반복하라는 구문이다.  WHILE 문의 블럭 내에서는 특정한 값을 변경하는 동작을 포함하며, 보통 이 변경되는 값 중의 하나를 루프의 종료 조건으로 사용한다. WHILE문을 이용해서 1부터 10까지의 자연수의 합을 구해서 출력하는 코드를 작성해보겠다.

s = 0
i = 1
while i <= 10:
  s += i
  i += 1
print(s)

이 코드에 대한 설명이다.

  • 먼저 s는 0으로 초기화하는데, 각 자연수를 누적해서 더한 값을 가리키는 이름이다.
  • i는 매번 증가하는 자연수가 된다. 그 초기값은 1이다.
  • while i <= 10: 을 통해서 i 가 10 이하인 경우에 계속 반복한다는 것을 지정한다.
  • s 는 i 만큼 증가하고,
  • 다시 i는 1씩 증가한다.
  • i 가 10일 때 s 에 한 번 더해진 후, i += 1 을 거쳐서 11이 될 것이다.
  • i가 11이 되면 조건을 더 이상 만족하지 않기 때문에 블럭은 더 이상 실행되지 않고 건너뛰게 된다.
  • s가 출력된다.

WHILE문의 조건이 블럭 외부에 있을 때도 있지만, 블럭 내부에서 정의되는 값이라면 조건절에 걸 수 가 없다. 예를 들어 5보다 큰 수가 입력되는 동안 입력된 값을 출력하려고 한다면,

n = int(input())
while n > 5:
  print(n)
  n = int(input())

똑같은 구문을 WHILE 블럭 안과 밖에 같이 써야 한다. 이것은 단지 WHILE의 조건절에 필요하기 때문에 이렇게 쓴 것이다. 이런 경우에는 무리해서 종료 조건값을 앞에서 정의하지 않고 while True: 로 무한 루프를 만들고, 반복 블럭 내에서 if + break로 탈출하는 패턴을 사용한다.

while True:  #1
  n = int(input())  #2
  if n <= 5:  # 3
    break
  print(n)  #4
  1. while True: 에서 보듯 조건은 항상 참이므로 이 루프는 무한루프를 돌게 된다.
  2. n은 블럭 내에서 처음 선언된다.
  3. 조건식을 통해서 break로 빠져나가는 길을 만들어 놓고
  4. 조건을 만족하지 않으면 출력한 후에 다시 while로 돌아간다.

CONTINUE 구문의 경우 잘 쓰면 코드를 깔끔하게 하는데 도움이 될 수 있다.  아래 코드가 어떤 일을 하는지 살펴보자.

i = 0
while i < 50:
  i += 1
  n = int(input())
  if n < 10:
    continue
  m = int(input())
  print(m + n)
  1. i 와 i < 50 으로 보아, 이 반복은 50번 동안 반복하게 된다.
  2. 루프를 시작하면 시행 카운터인 i 값을 올리고,
  3. 정수 하나를 입력받는다. (n)
  4. 입력받은 정수가 10보다 작으면 이후 작업을 생략하고 whie로 돌아간다.
  5. 입력받은 정수가 10이상이면, 또 다른 정수를 하나 더 입력받고
  6. 그 합을 출력한다. 그리고 다시 while로 돌아간다.

WHILE 문 정리

while문은 반복되는 참인동안 반복되는 if문이라고 생각하면 된다. 마지막 예제와 같이 특정 횟수만큼 반복되는 경우에는 for 문을 사용하는 것을 추천한다. 코드 자체가 간결해지는 효과도 있으면서, 흔히 하기 쉬운 실수를 방지할 수 있다. while 문을 사용할 때 흔히 하기 쉬운 실수로는 위의 경우에 i += 1 을 쓰는 것을 깜빡하고 빼먹어서 무한 루프를 돌게 된다거나, 종료 조건에서 i < 50 이라고 써야 할 부분을 i <= 50 이라고 쓰거나 혹은 그 반대로 써서 정확한 횟수만큼 반복하지 못하는 경우들이 있다.

따라서 예외적으로 전체 반복횟수를 알 수 없는 경우에 while문을 쓸 것.  그리고 가급적이면 탈출 조건을 명확하게 하기 위해서 중도에 탈출하는 무한루프의 형태로 사용하는 것을 권한다. 따라서 보다 권장되는 코딩 습관에 따라 위 코드를 다시 쓰면 다음과 같다. (그리고 억지로 continue문을 썼지, 이 문제는 continue가 없어도 된다.)

i = 0
while True:
  ## 탈출조건을 확인하고
  if i >= 50:
    break
  ## 탈출이 아닌 경우 조건 변수값을 변경한다.
  ## 이후, 반복 코드를 작성한다.
  i += 1
  n = int(input())
  if i > 10:
    m = int(input())
    print(m + n)

FOR 구문

FOR문은 파이썬에서 가장 유용하며 중요한, 강력한 반복구문이다. 파이썬에는 여러가지 타입의 “반복가능한” 값들이 있는데, FOR 문은 이 반복가능한 값에 대해서 매 반복을 시행하는 구문이다. 파이썬의 반복가능한 값에 대해서는 별도의 토픽에서 따로 설명하도록 하고 여기서는 간단한 FOR 문의 예제 몇 가지를 보도록 하자.

FOR문에서 일반적으로 가장 흔히 만나는 함수가 하나 있는데, 바로 range() 함수이다. 이 함수는 특정한 정수값 사이의 범위를 만드는데, 이 때 만들어지는 값이 반복가능한 값이다. range() 함수는 다음과 같은 세 가지 사용 방법이 있다.

  • range(10) : 0, 1, 2,… ,9까지의 10개의 값 범위를 만든다. 중요한 것은 10으로 주어지는 종료값은 범위에 포함하지 않으며, 범위의 시작이 0이라는 점이다. 따라서 범위에는 종료값은 포함되지 않지만, 전체 범위의 정수값은 종료값과 같은 개수를 가진다. (종료값이 범위에 포함되지 않는 것은 파이썬 여러 문법에서 일관적으로 나타나는 특성이다)
  • range(3, 10) :  이렇게 쓰이는 경우에 첫번째 값인 3은 시작값이며, 두 번째 값인 10은 종료값이다. 이 때에도 10은 포함되지 않으므로 이 범위는 실제로 3, 4, 5, 6, 7, 8, 9 가 해당한다.
  • range(3, 10, 2) : 종료값 뒤에 값을 하나 더 쓰면 이것은 간격으로 취급된다. 이 범위는 3, 5, 7, 9로 시작값으로부터 종료값보다 작은 범위 내에서 간격만큼 늘어난다. 참고로 간격이 음수로 주어지는 경우에는 시작값이 종료값보다 커야 한다. range(10, 3, -2)는 10, 8, 6, 4의 범위를 가리킨다.

즉, 복잡한 기술적인 개념이 없이, range() 함수는 특정한 범위의 연속적인 수열을 만들어내는 함수라고 생각하면 된다. 그 결과는 수열의 각 항에 대해서 반복가능한 값이다. 따라서 for 문은 range() 함수와 흔히 같이 쓰인다. 다음은 0~9까지의 정수를 출력하는 코드이다.

for i in range(10):
  print(i)

이 코드는 이렇게 이해하면 된다.

  1. range(10) 에 의해서 0, 1, 2, … , 8, 9의 정수들이 준비된다.
  2. i 는 매번 각각의 정수값이 되어 print(i)는 이를 출력하게 된다.
  3. 준비된 값들을 모두 사용하고나면 for 루프가 종료된다.

만약 이 코드와 동일한 동작을 하는 while 문 코드를 쓴다면 다음과 같을 것이다.

i = 0
while i < 10:
  print(i)
  i += 1

for i in range(10): 이라는 표현이 낯설어서 어렵게 느껴질 수 있겠지만, 이 FOR문은 WHILE문보다 훨씬 간결하다. 종료조건과 조건값의 갱신을 매번 수동으로 지정할 필요가 없으며, 따라서 이 과정에서 발생할 수 있는 실수의 여지가 없어진다.  FOR문을 쓰기 위해서는 IN 절에 사용할 반복가능한 어떤 값이 필요하지만, 이미 존재하고 있는 값을 쓰거나, range()를 이용해서 간단하게 정의할 수 있기 때문이다.

for 문의 본체가 되는 블럭은 해당 for 문에 의해서 반복되며, 블럭 내에는 또다른 if, while, for 문이 올 수 있다.  다음의 예는 구구단을 2단부터 9단까지 출력하는 예이다.

for x in range(2, 10): #1
  print(x, '단')  #2
  for y in range(2, 10): #3
    print(x, '*', y, '=', x * y) # 4
  print() #5
  1. x는 2~9사이의 범위이다. 이 코드에서 x는 2단, 3단, 4단..의 단을 의미한다.
  2. 각 단을 출력하기에 앞서서 ‘2 단’과 같이 현재 출력할 단의 이름을 한 번 출력한다. print() 함수는 컴마로 구분되는 여러 개의 인자를 넣어줄 수 있다. 각 인자는 공백으로 분리되어 순서대로 출력된다.
  3. y는 다시 2~9사이의 범위이다. 이 반복문은 각 단에 대해서 매번 반복되며, 각 단의 대표 숫자를 몇 배 할 것인지를 나타내는 값이다.
  4. print() 문을 사용해서 각 라인을 출력한다. x는 현재단의 값으로 고정되며, y는 각 단에서 2~9로 바뀌어나간다. x * y를 계산하여 그 결과까지 출력하게 된다.
  5. 각 단의 출력이 끝나면 구분을 위해서 빈 줄을 하나 출력할 것이다. print()에 아무런 값이 전달되지 않으면 빈 줄만 출력된다.

이 와 똑같은 동작을 하는 코드를 while 문을 쓴다면 어떨까? 먼저 각자가 한 번 코드를 작성해서 출력해보자. 그리고 단 한번에 제대로 출력되는 코드를 작성하였는지 체크해보자.

x = 2
while x < 10:
  print(x, '단')
  y = 2    ## 매 단마다 y를 초기화해야 한다. 이 위치가 틀리면 어이없는 결과가 출력된다.
  while y < 10:
    print(x, '*', y, '=', x * y)
    y += 1  ## 우주끝까지 출력하고 싶지 않다면 y를 잘 업데이트하라
  x += 1    ## 같은 이유로 x도 빼먹지 않고 업데이트를 잘해야 한다.

특히 이처럼 반복문이 중첩되는 경우에 WHILE 문이라면 조건을 변경시키는 코드의 위치가 잘못되거나, 아예 이런 코드를 빼먹기 쉽다. 따라서 앞으로는 피할 수 없는 상황이 아니면 WHILE 문은 쓰지 않는 것으로 알고 있자.

FOR – IN 문에서 사용되는 반복가능한 값에는 range 객체(range() 함수가 리턴하는 값)외에도 리스트나, 문자열, 집합, 사전…등 단일 값이 아닌 여러 원소의 집합으로 간주되는 값이 올 수 있다. 이러한 “반복가능한” 값을 파이썬에서는 연속열, 즉 시퀀스(sequence)라고 한다. 시퀀스는 그 정확한 타입을 막론하고 반복가능한(iterable) 공통적인 특성을 갖는다. 어떤 리스트 A의 각 원소에 대해서 그 제곱의 값을 출력하는 코드를 보자.

A = [3, 7, 2, 9, 4]
for a in A:
  print(a * a)

반복문의 관점에서 for 는 어떤 연속열의 각 원소에 대해 동일한 작업을 반복하는 것으로 볼 수 있는데, 한 편으로는 다음과 같이 생각할 수 있다.

  • 리스트나 그외 다른 연속열들은 결국 하나의 값이며, for 문은 이러한 연속열 하나에 대해서 어떤 코드 블럭을 그 내부로 밀어넣어 모든 원소가 동일한 변형을 적용받도록 하는 연산이다.
  • range에 대해서 생각해보면, 이는 기본적으로 하나(혹은 두 세 개)의 정수값으로부터 여러 개의 정수값을 만들어내는 역할을 담당한다. 즉 하나의 값이 여러 개의 값으로 populating되게 하는 문맥으로 이해할 수 있다.

  1. 앞으로 함수와 같이 실행가능한 이름을 언급할 때에는 ()를 뒤에 붙이도록 하겠다. 
  2. 여기서 함수는 어떤 값 -(변환)-> 다른 값으로 만드는 변환장치의 역할을 한다. 너무나 기초적인 비유라 보통 이 중요성을 잊기 쉬운데 여기에 주목하는 습관을 가지도록 하자. 

파이썬은 처음이라 – 값이란 건 처음이라

이번 글에서는 파이썬에서의 값에 대해서 살펴보겠다. 프로그래밍 언어를 배울 때에는 흔히 문법이나 알고리듬, 자료 구조와 같은 부분에 집중하면서 “값”이라는 부분, 즉 데이터 자체에 대해서는 소홀해지는 경향이 있다. 하지만 컴퓨터 프로그램은 본질적으로 데이터를 다루는 논리적인 기계장치이며, 어떤 입력값을 조작/변형처리하여 출력하는 구조를 가진다. 그 과정에서 다루어지는 값에 대한 특성을 이해하는 것은 값외의 다른 모든 것들을 이해하는 것과 같은 비중으로 중요하다.

컴퓨터에서 모든 값은 0과 1로 구성되는 이진수만이 통용된다는 것이 널리 알려져 있다. 그렇다면 어떻게 정수나 실수1, 텍스트와 같은 값들을 다룰 수 있는 것일까. 이러한 궁금증에서부터 시작하여 프로그래밍 언어에서의 값의 종류(타입)와 더불어 파이썬에서 사용되는 원시 값의 종류에 대해서 살펴보도록 하겠다.

값과 타입

컴퓨터는 전기적 신호를 통해 작동하는 기계이며, 전기신호의 ON/OFF를 구분하며, 이 ON/OFF를 조합하여 서로 다른, 즉 구분이 가능한 신호의 조합을 만들 수 있다. 이 때 ON을 1에, OFF를 0에 대응하면 일련의 ON/OFF 신호의 조합은 10110101011… 과 같은 표현으로 해석할 수 있다. 두 개의 숫자로 이루어지는 이 숫자체계를 수학에서는 이진수라고 하며, 약간의 산수를 통해서 이진수는 우리가 흔히 이해하는 10진수로 바꿔서 표시할 수 있다.

0과 1로 만들어지는 이진수는 결국 어떤 수(number)일 뿐인데, 우리가 개념적으로 사용하는 여러 가지 정보는 정확하게는 수 개념에만 한정되지 않는다. 실제로 우리는 수를 표현하기 위해서 숫자체계라는 것을 이용해서 숫자기호라는 문자를 사용한다. 사과에 오렌지를 더할 수 없듯이 “1”이라는 숫자2에 하나라는 의미의 수 1을 더할 수는 없다. 이렇 듯 우리의 관념에서도 어떤 정보는 그 타입이 구분되며, 어떤 타입들은 서로 호환되지만3 어떤 타입들은 서로 호환되지 않을 수도 있다.

정수나 float 값은 실수(Real Number[^3])라는 수 체계에서 볼 때는 같은 값이지만, 컴퓨터에서는 이진수를 사용해서 이를 표현하고 관리하기 위해서 서로 다른 기술적인 체계를 따르게 된다. 따라서 흔히 숫자라고하는 타입은 정수와 실수로 나뉘며, 보통의 프로그래밍 언어에서는 이들을 구분한다. 그외에 프로그래밍 언어에서 사용되는 가장 기초적인 데이터 타입에는 다음과 같은 것들이 있다.

  • 정수 : 0, 1, -1 과 같이 소수점 이하 자리가 없는 수. 수학에서의 정수 개념과 동일하다. 파이썬에서는 int 타입이라고 한다.
  • 실수 : 0.1, 0.5 와 같이 소수점 아래로 숫자가 있는 수. 파이썬에서는 float 이라고 한다.
  • 문자, 문자열 : 숫자 “1”, “a”, “A” 와 같이 하나의 낱자를 문자라 하며, 이러한 문자들이 1개 이상있는 단어/문장와 같은 텍스트를 문자열이라고 한다. 파이썬에서는 str 타입으로 분류한다. 특히 파이썬은 낱자와 문자열 사이에 구분이 없이 모두 str 타입을 적용한다.
  • 불리언값 : 참/거짓을 뜻하는 대수값. 보통 컴퓨터는 0을 거짓, 0이 아닌 것을 참으로 구분한다. 파이썬에는 bool 타입이라 부르며, 이 타입에는 TrueFalse 의 두 멤버만 존재한다. (참고로 각각은 대문자를 써야 한다.)
  • None : 존재하지 않음을 표현하기 위해서 “아무것도 아닌 것”을 나타내는 값이다.

이러한 기본적인 데이터 타입을 조합하여, 여러 개의 값을 하나의 단위로 묶어서 다루는 데이터 타입이 있다. 논리적으로 이들은 데이터 타입인 동시에 데이터의 구조(흔히 말하는 자료 구조)의 한 종류이기도 하다. 보통 다른 데이터들을 원소로하는 집합처럼 생각되는 타입들이다.

  • 리스트 : 순서가 있는 원소들의 집합. 파이썬에서 가장 중요한 데이터 타입 중 하나이다.
  • 튜플 : 순서가 있는 원소들의 묶음. 리스트와 혼동하기 쉬운데 단순히 하나 이상의 값을 묶어서 하나로 취급하는 용도로 사용된다.
  • 사전 : 그룹내의 고유한 이름인 키와 그 키에 대응하는 값으로 이루어지는 키값 쌍(key-value pair)들의 집합이다.
  • 집합/셋(set) : 순서가 없는 고유한 원소들의 집합.

자료 구조에 해당하는 데이터 타입은 매우 중요한데, 이 글에서 한 꼭지로 다루기에는 너무 중요하기 때문에 각각 별도의 글에서 다룰 예정이다.

숫자값들

수를 나타내는 타입으로 정수와 실수가 있다고 했다. 각각은 int 타입과 float 타입이다. 더하기, 빼기등의 산술 연산을 적용하는 값이며 가장 기본적인 수 개념들을 표현한다.

리터럴

어떤 값을 “써서 표현하는 방법”을 리터럴이라고 한다. 수를 표현하는 기본 리터럴은 다음과 같다.

  • 정수의 경우, 그냥 숫자들로 숫자값을 쓰면 된다. 0, 100, 123 과 같은 표현을 쓴다.
  • 실수의 경우, 중간에 소수점이 들어간다. 0.1,  4.2, 3.13123 와 같은 식으로 쓴다.
  • 0. 으로 시작하는 실수값에서는 흔히 앞에 시작하는 0을 뺄 수 있다. .5 는 0.5를 줄여쓴 표현이다.
  • 부호를 나타내는 – , +를 앞에 붙일 수 있다. (-1, +2.3 등)

보통 우리가 숫자를 쓰는 체계는 10진법이다. 파이썬에서는 드물게 다른 진법으로 숫자를 표현하기도 한다.

  • 기본적으로 그냥 숫자만 사용하는 경우, 이는 십진법 값으로 해석된다.
  • 파이썬이 인식할 수 있는 숫자 리터럴 체계에는 10진법외에도 이진법, 8진법, 16진법이 존재한다.
  • 이진법 숫자는 0b로 시작한다. 0b1010110 은 십진법 86을 숫자로 쓴 것이다.
  • 8진법 숫자는 0o로 시작한다. 위의 86은 8진법으로 썼을 때 0b126이다.
  • 16진법숫자는 0x로 시작한다. 16진법은 숫자외에도 a~f 까지의 문자를 포함하며, 이 글자들은 대소문자를 구분하지 않는다. 86은 16진법으로 표현시 0x56이다.

한편 큰 숫자를 다룰 때에는 _를 쓸 수 있다. 우리가 일상생활에서 큰 자리 숫자를 표현할 때 세자리마다 컴마를 찍는 것과 비슷하게 숫자 리터럴 중간에 _ 를 쓰는 것은 무시된다. 백만을 쓸 때 1_000_000 이라고 표기하면 정상적으로 1백만으로 인식되는데 1000000으로 쓰는 것보다 읽기 쉬을 수 있다. 언더스코어는 세자리마다 써야 하는 것은 아니고 원하는 아무자리에나 쓰면 된다.

숫자는 아니지만 참/ 거짓을 의미하는 부울대수값이 있다. 이들은 그 자체가 키워드로 True/False를 사용하여 표현한다.

True, False 가 키워드가 된 것은 파이썬3의 일이다. 파이썬2에서는 상수처럼 쓰이는 그냥 이름이었다. 따라서 True = False 와 같은 괴상한 구문이 아무런 문제없는 파이썬 코드였다.

연산

값을 가지고는 여러 연산을 할 수 있다. 다음은 기본적으로 지원되는 연산이다.

  • 더하기, 빼기, 곱하기, 나누기 :  각각 +, -, *, / 문자를 사용한다. 수식을 표현하는 방식은 계산기를 사용하는 방식과 똑같다.
  • 몫과 나누기 : // , % 를 사용한다.  (7 // 5, 13 % 8)
  • 거듭제곱 : ** 를 사용한다 ( 3 ** 2, 4 ** 3)
  • 비트연산 : & (and), | (or), ^ (xor), ~ (not) 이 있고, 시프트에는 << , >> 를 사용한다.
  • 비교연산 : 동등 및 대소를 비교할 수 있다.  참고로 ‘대소’비교는 ‘전후’비교가 사실은 정확한 표현이다. 비교 연산은 숫자값 뿐만 아니라 문자열에 대해서도 적용할 수 있다.
    • == , != : 같다, 같지 않다.
    • <, <=, >, >= : 작다, 작거나 같다, 크다, 크거나 같다. (좌변 기준으로)
  • 멤버십연산 : 멤버십 연산은 특정한 집합에 어떤 멤버가 속해있는지를 판단하는 것으로 비교연산에 기반을 둔다.
    • is, is not : 값의 크기가 아닌 값 자체의 정체성(identity)이 완전히 동일한지를 검사한다.
    • in, not in : 멤버십 연산. 어떠한 집합 내에 원소가 포함되는지를 검사한다. ('a' in 'apple')
  • 논리연산 : 비교 연산의 결과는 보통 참/거짓이다. 이러한 불리언값은 다음의 연산을 적용받는다. 참고로 불리언외의 타입의 값도 논리연산을 적용받을 수 있다. 논리연산을 위한 평가는 뒤에서 설명하겠다.
    • and :  두 값이 모두 참일 때 참
    • or : 한 값이 참이면 참
    • not : 단항 연산으로 우변의 값을 반전한다.

표현식

어떤 값 혹은 값들과 연산자를 함께 사용해서 수식을 표현한 것을 표현식(expression) 이라고 한다. 표현식을 평가식이라고도 하는데, 그 자체로 평가되어 하나의 결과값으로 축약된다. 따라서 1 + 1 과 같은 수식도 표현식이며, 0 과 같이 값 리터럴로 값을 표현해놓은 것 그 자체도 표현식이 될 수 있다. 이어서 소개할 문자열 역시 그 자체가 값이므로 표현식이며, 문자열과 관련되는 연산자들도 있다. 표현식은 궁극적으로 “평가”되며, 평가된다는 것은 표현식은 결국 하나의 값으로 수렴한다는 의미이다. 표현식은 간단한 수식으로 취급되고 있으나, 언어의 구조의 근간을 이루는 매우 중요한 개념이다.

앞에서 bool 타입이 아닌 숫자나 문자등의 다른 값들이 참/거짓으로 평가될 때 다음과 같이 판단된다.

  • bool : True는 참, False는 거짓이다.
  • int / float : 0이되면 거짓, 그외의 값은 참으로 평가한다.
  • str : 길이가 0인 빈 문자열은 거짓, 그외에는 참으로 평가한다.
  • 리스트, 사전 : 빈 컨테이너는 거짓, 그외에는 참으로 평가한다.
  • 임의의 객체 : None이면 거짓이며, None이 아닌 경우 값으로 평가하여 위의 규칙을 따르게 한다.

문자열

문자열은 글자 혹은 글자가 모여서 만드는 단어나 문장을 말한다. 크게는 이런 단어, 문장이 모여서 여러 줄의 단락이나 글 전체가 하나의 문자열이기도 하다.

리터럴

문자열 리터럴의 방식은 기본적으로 따옴표를 사용하는 것이다. 파이썬에서는 큰 따옴표와 작은 따옴표를 구분하지 않고 모두 문자열 리터럴에 쓴다. 하지만 괄호처럼 양쪽 따옴표가 맞아야 한다. (문자열을 둘러싸는 따옴표와 다른 따옴표는 문자열 내의 일반 글자로 해석된다. )

  • "apple" , 'apple' 은 모두 문자열 리터럴로 apple이라는 단어를 표현한 것이다.
  • 두 개의 문자열 리터럴이 공백이나 줄바꿈으로 분리되어 있는 경우에 이것은 하나의 문자열 리터럴로 해석한다. "apple,"   "banan""apple,banana"라고 쓴 표현과 동일하다.

따옴표를 세 개 연이어 쓰는 방법도 문자열 리터럴의 한 방법이다. 따옴표 세 개를 연이어서 쓰는 경우에는 문자열 내에서 줄바꿈이 그대로 허용된다. 흔히 함수나 모듈의 간단한 문서화 텍스트를 표현할 때 많이 쓰인다.

"""He said "I didn't go to 'SCHOOL' yesterday"."""  
=> He said "I didn't go to 'SCHOOL' yesterday". 를 그대로 표현할 수 있다.

=> 여러 줄에 대한 내용을 쓸 때.
'''HOMEWORK:
1. print "hello, world"
2. print even number between 2 and 12
3. calculate sum of prime numbers up to 100,000
''' 

그외에 이스케이프를 허용하지 않는 raw string 리터럴, 다른 값을 삽입하는 format string 리터럴, 바이트배열을 정의하는 bite string 리터럴등이 있는데, 조금 특수한 케이스이므로 여기서는 다루지 않겠다. (각각 r'...', f'...', b'...'와 같은 식으로 쓴다는 것 정도만 알고 넘어가자.)

연산

문자열도 엄연한 값이며, 가능한 연산이 있다.

  • 문자열 + 문자열 : 두 문자열을 연결할 수 있다.
  • 문자열 in 문자열 : 문자열 내의 특정 글자가 있는지 검사한다. ('a' in 'apple', 'c' not in 'banana')
  • 문자열 * 정수 : 문자열를 정수값만큼 반복하여 문자열을 만든다 ('abc' * 3 --> 'abcabcabc')

문자열에 실수를 곱하거나 문자열에 정수를 더하는 연산은 우리가 그냥 생각해도 어째야 할지를 모르겠고, 실제로도 정의되지 않았다. 이와 같은 연산들은 모두 ValueError 에러를 내게 되니 참고하자.

내삽 (interpolation)

내삽은 문자열에 대한 특별한 연산이다. 예를 들어 "Tom has 3 bananas and 4 apples." 라는 문자열이 있다고 할 때, 이것을 리터럴로 정의하는 것은 그 내용이 소스코드에 고정되는, 이른 바 하드 코딩(hard coding)이다. 문자열은 한 번 생성된 이후로 변경되지 않는 불변의 고정값이므로 Tom이 가지고 있는 사과나 바나나의 개수가 바뀌었을 때, 그 내용을 적절히 변경해 줄 수가 없다. 내삽(interpolation)은 문자열내에 동적으로 변할 수 있는 값을 삽입하여 상황에 따라 다른 문자열을 만드는 방법이다. 문자열 내삽의 기본원리는 문자열 내에 다른 값으로 바뀔 치환자를 준비해두고, 필요한 시점에 치환자를 실제 값의 내용으로 바꿔 문자열을 생성하는 것이다. 내삽의 방법에는 다음의 세 가지 방법이 있다.

  1. 전통적인 포맷 치환자를 사용하는 방법 : 문자열 % (값, ...)의 형식을 이용해서 문자열 내로 변수값을 밀어넣는 방법
  2. 문자열의 .format() 메소드를 사용하는 방법 : 치환자의 구분없이 사용할 수 있으며, 각 값을 포맷팅할 수 있는 장점이 있다.
  3. 문자열 포맷 리터럴을 사용하는 방법 : 포맷 메소드를 사용하지 않고 리터럴만으로 2.의 방법을 사용할 수 있다 (파이썬 3.6 이상)

전통적인 포맷 치환 방법

문자열 내에 어떤 값을 집어넣는것에 대한 필요는 아주 오래전부터 있어왔고, 이러한 치환자의 종류와 형식은 파이썬이 만들어지기 이전부터 일종의 표준으로 정의되어 자리잡고 있었다. 문자열 치환자는 퍼센트 문자 뒤에 포맷형식을 붙여서 치환자를 정의한다. 치환자를 포함하는 문자열과 각 치환자에 해당하는 값의 튜플을 (튜플을 아직 배우지는 못했지만…) % 기호로 연결하여 표현한다.

주요 치환자에는 다음과 같은 종류가 있다.

  • %d : 정수값을 나타낸다. d 앞에는 자리수와 채움문자를 넣을 수 있다. 예를 들어 %04d 라고 쓰면 앞의 0은 채움문자이고 뒤는 포맷의 폭이다. 즉 %04d 는 0으로 시작하는 네자리 정수를 의미한다. 13이라는 값을 포맷팅할 때, %d 에 치환하면 “13”이 되지만, %04d 에 치환되는 경우에는 “0013”으로 치환된다.
  • %f : 실수값을 나타낸다. f 앞에는 .3 과 같이 소수점 몇 째자리까지 표시할 것인지를 결정하는 확장정보를 넣을 수 있다. "%.3f" 라는 템플릿은 1.5를 “1.500”으로 표시해준다.
  •  그 외에 정수값은 숫자 리터럴과 같이 %b, %o, %x를 이용해서 각각 이진법, 8진법, 16진법으로 표시할 수 있다.
  • %s : 문자열을 의미한다.
  • %r : “representation”으로 타입을 구분하지 않는 값의 표현형을 말한다. 표시되고자 하는 값의 타입이 분명하지 않을 때 사용한다. 위에서 소개된 %d, %f, %s 에 대해서 올바르지 않은 타입의 값을 치환하려 하면 TypeError가 발생한다. 이럴 때 사용할 수 있다.

format 메소드를 사용하는 방법

파이썬의 기본 내장함수 중에서도 format() 함수가 있는데, 이 함수도 문자열의 format 메소드와 동일한 동작을 한다. format(문자열, 치환값)` 의 형식으로 사용하며 그외 내용은 이 절에서 설명하는 것과 동일하다.

전통적인 치환자구분이 타입을 가린다는 제약이 있고, 포맷팅의 방법이 제한되어 있다는 부분때문에 최근에 대세를 이루는 방식이다. 이 방식에서는 %d 와 같은 표현 대신에, { } 를 사용한다. { } 자체가 하나의 값을 의미하며, 번호를 부여해서 한 번 받은 값을 여러번 사용할 수 있다. 이 포맷팅 방식을 미니포맷이라고 불리는데 대략 다음과 같은 문법을 가지고 있다.

{ 치환자 }

치환자  ::= [필드이름] [!변환형] [: 포맷정의]
포맷정의 ::= [[채우기]정렬][부호][#][0][폭][그룹옵션][.소수점자리][타입]

뭔가 포맷방식이 엄청난데, 채우기와 폭은 전통적인 포맷 치환자에서도 지원하던 것인데, 왼쪽 정렬이나 중앙정렬도 설정할 수 있다. 숫자값인 경우에는 특별히 d, f, x, b, o 등의 표현타입도 정의해줄 수 있다. 이 부분을 더 깊게 파고들려면 많은 분량이 필요하므로, 앞으로 진도를 나갈 때 예제 등에서 사용하면서 설명하도록 하겠다.

None

객체의 개념에 대해서 아직 설명하기 전이라 None을 명확하게 설명하기는 어렵다. 다만 어떤 값이 없는 상태를 가리킬만한 표현이 마땅히 없기 때문에 “아무것도 없다”는 것으로 약속해놓은 어떤 값을 하나 만들어 놓은 것이다. None 이라고 대문자로 시작하도록 쓰며, 실제 출력해보아도 아무것도 출력되지 않는다. 값이 없지만 False 나 0 과는 다르기 때문에 어떤 값으로 초기화하기 어려운 경우에 쓰기도 한다.

 

정리

이상으로 가장 기본적인 파이썬의 값 타입들에 대해서 살펴보았다. 물론 리스트와 사전, 그리고 튜플과 같은 다른 타입들이 있지만, 분량 관계상 이 글에서 계속 다루는 것 보다는 이쯤에서 한 번 끊고 가는 것이 좋겠다. 계속해서 다음 포스팅으로 이어나가시라….


  1. floating number. 0.5와 같이 소수점 이하 자리를 갖는 수를 말한다. 
  2. 이 글에서는 관례를 따라 숫자값을 표현할 때는 숫자만 쓰고, 문자 자체를 표현할 때는 따옴표를 적용할 것이다. 
  3. 1 + 0.5 를 계산할 수 있듯이 정수와 실수는 몇 가지 연산에서 호환이 가능하다. 이 타입간 호환은 사용하는 프로그래밍 언어에 따라 제약이 있을 수 있다. 

(연재) 파이썬은 처음이라 – 문법은 처음이라

이번 시간에는 파이썬의 문법에 대해서 알아볼 생각인데, 가능하면 딱딱하지 않은 내용으로 소개하고 싶었지만 그래도 문법의 구조에 대해서는 잠깐 언급하고 넘어가는게 좋겠다. 아마 교과서 같은 책이라든지 파이썬 공식 문서에서도 이런 식의 문법 구조 설명은 나올텐데, 대부분 “너무 빤한 이야기라서 왜 써있는지도 모르겠고 그냥 대충 몰라도 아는 이야기라 넘어간다”며 읽지 않는 것이 대부분이다. 절대로 재밌는 내용이 아닌데, 책을 몇 장 넘겨보면 print(“hello world”) 같이 쉽고, 실행까지 해볼 수 있는 재밌는 예제가 있기 때문에 대충 넘어가는 것이 현실이다. 또한, 많은 경험자(?)들이 문법은 실제 예제를 통해서 그 사용패턴을 익히는 것으로 충분하다고 이야기하는 경우도 있다.

그럼에도 불구하고 이런 개념들은 사실 가르치기 편하려고 만들어 놓은 것이 아니라, 언어 체계의 수학적 엄밀성을 위해 정의해 놓은 것이며 이는 언어 전반의 디자인 원리에 해당한다. 이렇게 생각해보자. 간단한 프로그래밍 문제가 있다. 1부터 10까지의 합을 구하는 것일수도 있고, 특정 웹주소의 HTML 문서를 읽어와서 그 소스를 출력하는 것일 수도 있다. 우리는 이런 문제를 (당장은 몰라도) 파이썬으로 해결할 수 있고, 그 말은 파이썬이라는 언어를 이용해서 이러한 문제를 풀기위한 모든 절차를 기술(describe)할 수 있다는 것을 의미한다. 그런데, 굳이 파이썬이 아니더라도 우리는 그 과정을 기술할 수 있다. 물론 그렇게 한국어로 써놓은 (혹자는 영어가 편할 수도 있겠고) “레시피”는 그것을 해석해서 실행해주는 해석기는 존재하지 않지만 개념상 파이썬 소스코드와 동일한 사상을 표현한 것이라 할 수 있다. 즉 우리는 우리의 일상어를 사용해서 프로그래밍 소스 코드를 작성할 수(는) 있다.

이 말은 역설적으로 파이썬이라는 프로그래밍 언어가 사실은 한국어나 영어와 같은 “진짜 언어”라는 것을 증명한다. (괜히 프로그래밍 ‘언어’라고 하는 것이 아니다.) 우리의 감성이나 애매한 뉘앙스를 전달하기에는 부족하지만, 수학적으로 엄밀하게 디자인되어 명확한 수학적 행위를 써내려 나갈 수 있도록 디자인되어 있고, 따라서 수학적이고 논리적인 생각의 타래를 구성하는 프레임으로서도 기능할 수 있다. 만약 여러분이 제주도로 여행을 간다면, 머리속으로 제주 여행 계획을 떠올릴텐데, 그 때 여러분이 생각할 때 사용하는 언어는 무엇인가?

어떤 언어의 문법은 그 언어가 지향하고자 하는 철학에 따라 고안된다. 프로그래밍을 시작할 때 첫 언어가 중요한 것도 여러 실용적인 이유보다는 여기에 있어야 한다. 프로그래밍을 C로 배운 사람은, 그 이후에 어떤 언어를 접하든 간에 표현만 바뀐 C 코드를 짜게 된다. 즉 C보다도 풍부한 표현이나 기능을 기반으로 한 생각을 거의 하지 못하게 된다는 이야기이다. 즉, 프로그래밍 언어의 문법은 생각을 표현해내는 도구인 동시에, 생각이 구체화되는 틀이기도 하다는 점에서 문법에 대한 올바른 이해는 아주 필수적인 과정이라 불 수 있다. 다행히 파이썬의 문법은 1) 그 규칙의 수가 적고 2) 대부분의 규칙이 일관된 맥락을 가지고 있어서 이해하고 배우기 쉽다. 그러면 파이썬 문법에 대해서 본격적으로 이야기해보도록 하자.

문법의 구성요소

프로그래밍 언어도 명백히 언어이며, 각 단위의 구성요소들이 그 나름의 질서를 가지고 결합된다. 다음은 일반적인 언어의 구조를 이루는 요소들이다.

  • 표현식 : 표현식은 어떤 값이나, 값과 연산자를 결합한 수식을 말한다. 이를테면 5, 3 * 4, "hello" + ", world" 와 같은 표현들이 표현식에 해당한다. 표현식은 이후 “평가”되면서 하나의 특정한 값으로 축약된다.  2 + 3 이라는 표현식은 평가될 때 계산되어 5라는 값으로 축약되는 식이다.
  • 구문(statement) : 구문은 몇가지 예약어와 표현식을 결합한 패턴이며, 컴퓨터가 수행해야 하는 하나의 단일 작업(instruction)을 명시한다. 구문에는 다음과 같은 몇 가지 종류가 있다.
    • 대입문 (assigning statment) : 파이썬에서는 대입이라는 표현대신에 ‘바인딩(binding)’이라는 표현을 쓰는데, 대입문은 어떤 값에 이름을 붙이는 작업이다.
    • 분기문 : 조건에 따라 수행할 작업을 나눌 때 사용한다. if 문이 여기에 해당한다.
    • 반복문 : 특정한 작업을 반복수행한다. for 문 및 while 문이 여기에 해당한다.
  • 블럭 : 블럭을 여러 구문이 순서대로 나열된 덩어리를 말한다. 블럭은 여러 줄의 구문으로 구성되며, 블럭 내에서 구문은 위에서 아래로 쓰여진 순서대로 실행된다. 블럭은 분기문에서 조건에 따라 수행되어야 할 작업이나, 반복문에서 반복적으로 수행해야 하는 일련의 작업을 나타낼 때 사용하며, 클래스나 함수를 정의할 때에도 쓰인다.
  • 정의 : 재사용이 가능한 독립적인 단위를 정의하는 것은 별도의 선언 문법과 그 내용을 기술하는 블럭 혹은 블럭들로 구성된다. 파이썬에서는 기본적으로 함수나 클래스를 정의하는 문법이 여기에 해당한다.

구문과 블럭

기본적인 구문(statememt)의 문법을 살펴보자.

바인딩 구문

바인딩 구문은 다른 언어에서는 흔히 대입문이라 불리는 구문이다. 하지만 파이썬의 구조상 어디에도 ‘대입’은 일어나지 않는다. 그리고 ‘대입’의 개념이 오히려 오해나 혼란을 일으킬 가능성이 있기 때문에, 이 강좌에서는 더 이상 대입이라는 표현을 의식적으로 지양할 것이다.

바인딩은 어떠한 값에 이름을 붙이는 일을 말한다. 즉 이름과 값을 연결(bind)하는 구문이다. 바인딩 구문의 구조는 다음과 같다.

{식별자} = {표현식}

바인딩 구문은 그저 “식별자”를 “표현식”에 = 로 연결한 것에 지나지 않는다. 그러면 식별자는 무엇인가?

식별자

식별자는 어떤 값을 프로그램 내에서 지칭하는 이름을 말한다. 하나의 이름은 당연히 하나의 값을 가리키는데 사용되므로 중복되지 않아야 하며, 파이썬 문법을 구성하는 예약어 (for, is, in, if 등)는 사용할 수 없다. 그외에 숫자를 포함할 수 있으나 숫자로 시작할 수 없다는 점등은 대부분의 프로그래밍언어가 내세우는 식별자 규칙이기 때문에 알아두는 편이 좋다.

바인딩 구문은 식별자와 표현식을 등호로 연결한다. 바인딩된 이후에는 식별자를 쓰면 해당 값을 지칭하는 표현이된다. 변수와 관련해서는 이름 공간이나 스코프 등의 별도로 다루어야 할 내용이 있으니, 이 부분은 나중에 다시 살펴보자.

바인딩 구문 :: 식별자 = 표현식
number = 1
total_amount = 1 + 2 + 3

블럭

블럭은 하나 이상의 구문의 집합이 같은 맥락상에 놓여있는 것을 말한다. 많은 프로그래밍 언어에서는 이러한 블럭을 중괄호({ … })를 이용해서 감싸는데, 파이썬에서는 괄호를 사용하지 않고 들여쓰기를 사용한다. 블럭은 현재 진행되는 코드 흐름의 하위 문맥을 의미하기도 한다. 따라서 조건문 및 반복문의 본체와 함수 정의의 본체등에 쓰인다. 블럭은 다른 코드와 들여쓰기로 구분되며 통상 새로운 블럭을 시작하는 구문은 그 끝이 콜론(:)으로 끝나면서 이어지는 블럭의 시작은 들여쓰기를 적용한다.

{조건문, 반복문 또는 함수/클래스 선언}:
  {블럭}

동일 블럭내의 모든 구문은 같은 개수만큼의 탭 혹은 스페이스를 사용해서 들여쓴다. 탭이나 스페이스 어떤 것을 써도 관계없지만, 탭과 스페이스를 섞어서 쓰는 것은 허용되지 않는다. 또한 들여쓰기의 폭은 코드 내에서 일관되어야 한다. 위에서 4칸으로 들여쓰고, 뒤에서 2칸으로 들여쓰는 것은 허용되지 않는다.

또한 블럭은 그저 들여써진 구문의 집합이므로, 블럭 내에는 조건문이나 반복문이 올 수 있으며 블럭 안에 블럭이 들어가는 모양의 코드도 구성될 수 있다.

IF 문

if 문은 파이썬 내의 유일한 조건문이다. 기본적인 if 구문의 구조는 이렇게 생겼다.

if {조건식}:
  {조건이 참일 때 실행될 블럭}

조건식은 참 혹은 거짓으로 평가되는 표현식이다. 표현식을 평가(계산)한 결과가 참이면 바로 아래에 이어지는 블럭이 실행되고, 그렇지 않으면 해당 블럭의 실행을 건너뛴다. 다음은 간단한 if 문의 실제 예이다.

a = 4  #1
if a > 3:  #2
  print('a is larger than 3') #3

if a > 10: #4
  print('nope') #5
  1. 첫번째 a = 4 는 바인딩 구문이다. 이제 a 는 4라는 값을 가리킨다.
  2. if 문의 조건은 a > 3 이라는 표현식이다. a는 4이므로 4 > 3 으로 평가되고, 이것은 명백히 ‘참’이다.
  3. 따라서 “a is larger than 3″이라는 내용이 출력된다.
  4. 그런데 또다른 조건문에서는 4 > 10은 거짓으로 평가된다.
  5. 따라서 print('nope')은 실행되지 않을 것이며, 이 메시지가 출력되지도 않을 것이다.

ELSE 절

기본적인 if 문은 조건값에 따라 블럭A를 실행할 것인지 실행하지 않을 것인지로 분기한다. 하지만 경우에 따라서는 조건에 따라 블럭A를 실행할 것인지 블럭B를 실행할 것인지의 경우로 나뉘는 경우도 있다. 이를 위해서 IF 문은 선택적으로 ELSE 절을 사용할 수 있다.  ELSE 절은 단독으로 사용될 수 없으며 IF 구문의 일부로만 사용된다. 다음과 같은 구조를 갖는다.

if {조건식}:
  {조건이 참일 때 실행될 블럭A}
else:
  {조건이 거짓일 때 실행될 블럭B}

ELIF 절

IF 문에서 사용되는 조건값은 참/거짓의 둘 중 하나의 상태로만 평가된다. 따라서 하나의 IF 문이 선택할 수 있는 분기는 두 가지 밖에 없다. (참일때와 거짓일 때) 그렇다면 그 보다 많은 경우의 수를 고려해야 하는 경우에는 어떻게 해야할까?

조건A ---> 블럭a
      └-> 조건B ---> 블럭b
                └-> 조건C ---> 블럭c
                          └-> ...

여러 선택지를 정의하려는 경우, 위와 같이 그만큼 여러 개의 조건이 필요하며, 각 조건을 순차적으로 판단한다. 이 때에는 ELIF 절을 사용한다. ELIF 절은 ELSE IF를 줄인 것으로 원래 검사했던 조건을 만족하지 않을 때, 제 2, 제 3의 조건을 검사하는 것으로, IF 문에서 선택적으로 사용되며 다음과 같이 구성된다. ELIF 절과 ELSE 절은 서로 독립적이며, 각각이 선택적이기 때문에 ELIF 를 썼다고 반드시 ELSE를 써야하는 것은 아니다.

if {조건식A}:
  {블럭A}
elif {조건식B}:
  {블럭B}
...
else:
  {블럭Final}

WHILE 문

WHILE 문은 기본적인 반복문으로 특정한 반복 조건을 두고, 조건이 참을 만족하는 동안 정의된 블럭을 반복하여 실행한다. WHILE 문의 문법 구조는 다음과 같이 단순하다.

while {조건식}:
  {블럭}

일반적으로 조건식은 어떠한 변수에 대한 표현식이며, 루프 블럭 내에서는 이 조건값이 변경되어 특정한 상황에서 루프를 탈출할 것인지를 지정해야 한다. 조건식은 IF 문와 마찬가지로 참/거짓으로 판단되는 표현식이며, 처음부터 거짓으로 평가되는 경우에는 블럭을 아예 실행하지 않는다.

WHILE 문의 실행 순서는 다음과 같이 처리된다.

  1. 파이썬 코드는 기본적으로 위에서부터 아래로 진행한다.
  2. while 문을 만나면 파이썬은 조건식을 평가한다. 평가된 값이 참으로 나타나면 블럭으로 실행 흐름을 진행한다.
  3. 블럭의 끝에 다다르게 되면 다시 while 문이 있던 위치로 돌아간다. (그리고 조건을 다시 평가한다.)
  4. 블럭 내에서 흐름 제어 구문을 만나면 해당 중간 위치에서 블럭을 탈출하거나, while 문의 위치로 돌아갈 수 있다.

만약 맨 처음 while 문 위치에서 조건식을 평가한 결과가 False인 경우라면, 블럭은 실행되지 않고 바로 건너뛰게 된다. 참고로 이 경우에는 블럭을 중간에 탈출한 경우가 아니라 “반복을 완료”한 것으로 간주된다.

BREAK / CONTINUE 문

BREAK와 CONTINUE 구문은 반복문 내에서의 흐름 제어에 사용된다. 이 두 구문은 WHILE 문과 뒤에 소개할 FOR 문에서 공통적으로 쓰일 수 있다. BREAK 문은 현재 속해있는 루프를 중단하고 즉시 빠져나간다.  BREAK문은 break 라는 단일 키워드로 구성된다. WHILE문 내에서 사용된 break는 다음과 같이 쓰인다.

while {True}:
  {블럭A}
  break
  {블럭B}  -> 흐름은 여기까지 진행되지 않는다.

이 때 break 를 만나면 즉시 루프 블럭을 탈출하기 때문에 위 예에서는 블럭B가 실행되지 않으며, 블럭A는 한 번만 실행된다.

CONTINUE문은  BREAK문과 달리 루프를 탈출하지 않는다. 대신에 해당 반복회차가 CONTINUE 문에서 완료된 것으로 간주하고 반복문의 처음인 WHILE 문으로 돌아가서 조건식을 다시 평가하고 다음 반복회차를 시작한다. CONTINUE 문도 BREAK와 마찬가지로 continue 라는 단일 키워드로 구성된다.

while {조건식}:
  {블럭A}
  continue
  {블럭B}

위 구조에서 조건식이 참인 경우에, 루프에 진입하게 되고, 블럭A가 실행된다. continue 를 만나게 되면 실행흐름은 다시 while로 돌아가기 때문에 블럭B는 실행되지 않지만, 블럭A는 조건식이 참으로 평가되는 동안에는 계속해서 실행될 것이다.

ELSE 절

흔히 잘 다뤄지지 않고 넘어가는 부분인데, 파이썬에서는 특이하게 반복문에 대해서 ELSE 절을 사용할 수 있다. ELSE 절은 WHILE 문이나 FOR 문에 공통적으로 사용할 수 있는데, “중간에 BREAK문을 통해서 탈출하지 않고 루프를 완료하였을 때” 실행되는 구문이다. 흔하게 쓰이는 문법은 아니지만, 중도탈출하지 않고 루프를 완료하였는지 여부를 추가적인 로직을 사용하지 않고 언어의 기능으로만 구분할 수 있다는 점에서 유용한 경우가 있을 것이다.

while {조건식}:
  {블럭A}
  if {탈출조건식}:
    break
  {블럭B}
else:
  {블럭C}

루프의 종료를 결정할 조건식과 별개로 탈출 조건식을 걸고 IF 문을 사용해서 루프의 블럭 중간에서 탈출할 수 있는 코드의 모양이다. 이 때 break 를 만나서 중간에 탈출한다면 블럭B와 블럭C는 실행되지 않을 것이다.

만약 초기조건부터 만족하지 않아서 WHILE 문의 루프 블럭을 전혀실행하지 않는 경우에도 WHILE문은 완료된 것으로 간주하기 때문에 (왜나면 반복조건에 의해서 0회 시행후 완료된 것으로 본다.) 이 때에도 ELSE문은 실행된다.

FOR 문

FOR문은 또 다른 형태의 반복문이다. 사실 몇몇 함수형 언어를 제외한 대부분의 프로그래밍 언어가 기본적인 반복문으로 FOR문을 가지고 있다. 파이썬의 FOR 문은 다른 언어들의 FOR 문과는 개념상 차이가 있다. 예를 들어 C의 FOR 문은 초기값, 완료조건, 변경의 세 가지 조건에 의해서 초기조건을 반복적으로 변형하여 완료조건에 이르면 더 이상 반복하지 않는 구조이며, 이는 “완료조건을 검사하여 루프를 탈출”한다는 개념에서 WHILE 문과 본질적으로 크게 다르지 않다.

하지만 파이썬의 FOR 문은 “이터레이션(iteration)” 혹은 “순회”의 개념이라 할 수 있다. 파이썬에는 “반복가능한”(iterable) 타입의 값들이 있는데, 쉽게 생각해서 어떤 값들이 줄지어 있는 수열 같은 것을 생각하면 된다. 줄지어 있는 값들에 대해서 각각의 값에 대해서 블럭을 반복실행하는 것이며, 따라서 조건에 의해 탈출 여부를 결정하지 않는다. 1

FOR 문의 구조는 다음과 같다. FOR 문은 IN 과 항상 짝을 이뤄 사용되기 때문에 FOR – IN 문이라고도 한다.

for {식별자} in {반복가능한 값}:
  {블럭}

for ... in 사이에 사용되는 식별자는 반복가능한 값에서 매 원소값에 바인딩되고, 블럭내에서 이 이름이 사용되면 매 시행마다 다른 값이 된다는 말이다. “반복가능한 값”에 대한 내용은 별도의 토픽에서 따로 다루도록 하겠다.

그외에 FOR – IN 구문은 “축약(Comprehension)”이라는 매우 중요한 표현 문법의 근간이 된다. 이 역시 이후 강좌에서 매우 자주 설명될 것이므로 지금은 “축약”구문이라는게 있다 정도만 알아두도록 하자.

선언 문법

함수나 클래스와 같이 재사용가능한 코드를 정의할 때는 선언 문법을 쓴다. 함수와 클래스의 선언문법이 각각 다르므로 따로 설명하겠다. 각각의 정의에 관한 자세한 설명은 다음에 따로 다루도록 하겠다.

함수의 정의

함수는 def 키워드를 통해서 정의하며, 하나의 블럭이 된다. 함수는 보통 RETURN 문으로 끝나는데, 생략되는 경우에는 None을 리턴하는 것으로 암묵적으로 간주된다. 다음은 일반적인 함수 정의 방법이다.

def {함수이름식별자}({파라미터들}):
  {블럭}
  return {리턴값}

함수이름은 여느 식별자와 같은 규칙을 따라 명명할 수 있다. 함수 이름 뒤에는 괄호 속에 파라미터를 정의하는 부분이 온다. 파라미터는 함수가 받아들이는 각각의 입력값들이며, 함수의 기능과 용도에 따라 없거나, 하나 혹은 그 이상의 파라미터가 정의될 수 있다.

클래스의 정의

파이썬을 시작하는 시점에서 클래스를 직접 디자인해서 사용할 일은 없기 때문에 간략하게 구조만 설명하겠다.

class {클래스명}({부모클래스명}):
  def __init__(self, {파라미터,...}):
    {초기화 블럭}
  def {메소드명}(self, {파라미터,...}):
    {블럭}
  ...

파이썬의 모든 값은 사실 객체이며, 객체는 어떤 상자안에 데이터 혹은 상태와 호출가능한 함수를 넣어놓은 것이다.  객체에 연결된 값 및 함수들은 모두 속성이라는 이름으로 불린다. 클래스는 같은 속성의 세트를 가지고 있는 객체들을 만들 수 있는 설계도에 해당한다. 그리고 계속 말하지만, 아직 자세히 몰라도 되며, 사실 클래스를 안쓰고도 프로그램을 작성하는 것도 얼마든지 가능하다.

정리

이상으로 파이썬의 가장 기본이 되는 문법들을 살펴보았다. 프로그래밍은 이러한 문법 구조를 이용해서 값을 입력받고, 계산/변환하고, 출력하는 흐름을 만드는 일이며, 따라서 문법과 같이 중요한 것은 바로 “값”이다. 다음 시간에는 이 “값”에 대한 이야기를 좀 해보려고 한다.


  1. 그럼에도 불구하고 FOR 문의 내부에서 IF 문과 BREAK 문을 적절하게 결합하여 탈출조건에 의한 중도탈출도 가능하다. 

(연재) 파이썬은 처음이라 – IDLE은 처음이라

아주 당연한 이야기지만 어떤 프로그래밍 언어도 책으로만 공부할 수는 없다. 프로그래밍 언어에서 가장 중요한 것은 직접 코드를 타이핑해서 쳐보고, 그것을 실행해보는 경험이다. 이것은 단순히 책에 쓰여있는 글자를 그대로 타이핑해서 실행해보고 “아 이 코드는 되네”라는 경험을 말하는 것이 아니라, 책에 쓰여진 코드에서 어떤 부분을 바꿔보고 그것은 동작하는지를 예상한 후 확인해보는 것을 포함해야 한다. 제대로 실행이 되지 않거나 생각했던 것과는 다른 결과가 나온다면 다시 그것은 왜 그런지를 생각해보거나, 찾아보아야 한다. 그렇게 해서 답을 알게 되었다면 다시 예상했던 결과를 만들기 위해서 작성했던 코드를 어떻게 바꿔야 하는지를 고민해보고, 실험해 본다.

각각의 프로그래밍 코드 조각은 작은 레고 블럭과 같아서, 그 자체로보면 하나도 대단한 것이 없어 보이고 또 이러한 코드를 충분히 많이 이어 붙여서 대단한 구조물을 만들 수 있을 것 같은 생각이 든다. 이것은 그 자체로는 잘못되거나 틀린 생각은 아니다. 다만, 단순히 벽돌로 벽만 쌓아올려서는 벽난로가 있는 거실은 만들 수 없으며, 벽난로를 만들기 위해서는 “벽 쌓기”이상의 조립 기술이 필요하다. 마찬가지로 프로그래밍 작업을 보다 큰, 그리고 조금 더 실용적인 관점에서 보았을 때 코드 조각과 언어가 제공하는 기능으로 모든 것을 만들기 보다는 이미 만들어진 코드를 이해하고 활용하는 방법을 익히는 것 역시 중요하다.

만약 운이 좋다면 문서화가 잘 되어 있는 기성 코드를 만날 수 있을지 모르지만, 그렇지 않은 경우에는 직접 부딪혀가며 해결해야하는 문제가 많이 있다. 이럴 때, 아주 간단하고 쉬운 방법으로 코드를 테스트할 수 있는 도구가 있으면 좋을 것이다. IDLE은 파이썬을 설치하면 기본적으로 제공되는 간단한 인터랙티브 쉘 + 편집기로 “미지의” 코드를 탐색하고 테스트하기에 유용한 도구이다.

쉘(Shell)이란 ‘껍질’이라는 의미의 영어 단어로, 실제 파이썬 코드를 해석하고 실행해주는 파이썬 인터프리터를 감싸고 있다는 의미로 사용된다. 본래 OS의 쉘에서 파생하였다. OS가 컴퓨터에서 중요한 이유는 컴퓨터 시스템 하드웨어를 제어하고, 실행되는 각각의 프로그램 프로세스에 대해 리소스를 할당하는 일을 한다. 프로그램 커널을 통해서 특정한 명령을 실행하는 등의 일을 처리하기 위해서는 커널에 명령을 전달하고, 그 결과를 볼 수 있도록 커널과 사용자를 연결해주는 무언가가 필요한데, 이 때 쉘을 사용한다.

인터랙티브 쉘

대화형 쉘. 프로그래밍 언어의 도구라는 측면에서는 REPL이라고도 한다. REPL은 Read – Evaluate – Print – Loop의 머리글자를 합친 용어로 사용자로부터 한 줄 단위로 명령어 구문을 입력 받아, 이를 매번 평가하고 그 내용을 출력하는 것을 반복하는 프로그램을 말한다. IDLE에서 기본적으로 보이는 쉘이 바로 이 REPL의 대표적인 예이다.

편집기

Editor. 포토샵과 같은 프로그램을 “이미지 편집기”라고도 부르는데 (어째 포토샵은 그 자체로 하나의 어플리케이션 도메인이 되어버린 것 같지만…) 일반적으로 편집기라고 하면 “텍스트 편집기”, 즉 텍스트 파일을 열어보고 내용을 수정할 수 있는 프로그램을 말한다. 가장 기본적인 편집기로는 메모장이 있다. 다만 메모장 같은 편집기로는 프로그램 소스 코드를 작성하거나 편집하기가 매우 불편하다. 대부분의 쓸만한 편집기들은 문법 하이라이팅이나 자동완성 및 그외 몇 가지 편리한 부가기능을 제공한다.

IDLE

IDLE은 파이썬이 설치될 때 같이 설치되는 파이썬으로 만들어진 편집기 겸 인터랙티브 쉘이다. IDLE을 처음 실행하면 왼쪽과 같은 모습을 볼 수 있다.

이는 명령 프롬프트(혹은 터미널)에서 파이썬을 그냥 실행했을 때와 똑같은 모양이다. >>> 는 파이썬 쉘의 프롬프트로 지금 라인이 사용자가 명령을 입력하는 행이라는 의미이다. 명령을 입력하고 엔터를 누르면 해당 명령이 해석되고 처리되어 그 결과가 아래에 출력된다.

이 연재에서도 코드 입력창에서 >> 라는 글자로 시작하는 부분은 모두 이 인터랙티브 쉘에서 입력한 것으로 간주한다. (경우에 따라서는 In [1]: 과 같이 표시하는 경우도 있는데, 이것은 ipython 쉘의 프롬프트이다.)

IDLE은 주어진 명령을 그 때 그 때 해석해서 처리하며, 변수를 정의하는 것들은 기억하게 된다. 다소 불편한 감은 있지만 반복문이나 분기문 혹

은 함수를 작성하는 등의 여러 줄 입력도 가능은 하다. 하지만 소스 코드의 양이 조금이라도 길어진다면 여러 줄의 명령을 한꺼번에 작성하고 한 번에 실행하는 것이 편할 것이다. IDLE은 이 경우에도 여러분의 좋은 친구가 될 것이다. File 메뉴에 New 를 선택하면, 이번에는 프롬프트가 없이 빈 창이 표시된다.

이 창에서는 명령을 입력한 후에 엔터를 쳐도 아무 일이 일어나지 않는다. 이 창은 마치 메모장과 같이 파이썬 소스코드를 작성하는 편집기 창이다. 소스코드를 작성하고 저장한 후 F5 키를 눌러서 실행하면, 맨 처음 IDLE을 켰을 때 나왔던 쉘 창에서 실행 결과가 출력된다.

이 글을 읽는 각자가 파이썬 실행환경을 가지고 있을 것이다. 글과 같이 IDLE을 이용하는 사람도 있을 것이고, 많지는 않지만 ipython을 설치해서 쓰거나, 아니면 sublime text와 같은 별도의 편집기나 PyCharm 같은 통합 개발 환경을 사용하는 사람도 있을 것이다. 우선은 자신에게 맞는, 혹은 현재 배우고 있는 과정에서 사용하는 환경에 익숙해지는 것이 중요하다. 하지만 파이썬 개발에서 가장 기본이 되는 IDLE도 엄청 큰 프로젝트를 만드는 일이 아니라면 충분히 요긴하게 쓰일 수 있으니, 사용법을 조금 익혀 두도록 하자.

참고로 “파이썬 하루동안 가지고 놀기”라는 아주 오래된 고전 에세이가 하나 있다. 일독을 권한다.