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

이번 시간에는 파이썬의 문법에 대해서 알아볼 생각인데, 가능하면 딱딱하지 않은 내용으로 소개하고 싶었지만 그래도 문법의 구조에 대해서는 잠깐 언급하고 넘어가는게 좋겠다. 아마 교과서 같은 책이라든지 파이썬 공식 문서에서도 이런 식의 문법 구조 설명은 나올텐데, 대부분 “너무 빤한 이야기라서 왜 써있는지도 모르겠고 그냥 대충 몰라도 아는 이야기라 넘어간다”며 읽지 않는 것이 대부분이다. 절대로 재밌는 내용이 아닌데, 책을 몇 장 넘겨보면 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 문을 적절하게 결합하여 탈출조건에 의한 중도탈출도 가능하다.