변수, 표현식, 문장

값과 형

글자나 숫자처럼, 은 프로그램이 다루는 가장 기본적인 것들 중 하나입니다. 우리가 지금까지 만난 것들 중에는 1, 2, 'Hello, World!' 같은 것들이 있습니다.

이 값들은 각기 다른 에 속합니다: 2는 정수고, 'Hello, World!'문자열입니다. 따옴표로 둘러싸여 있기 때문에, 여러분(과 인터프리터)는 문자열을 구분할 수 있습니다.

만약 값이 어떤 형에 속하는지 모르겠다면 인터프리터에 물어볼 수 있습니다.

>>> type('Hello, World!')
<type 'str'>
>>> type(17)
<type 'int'>

별로 놀랍지 않게도, 문자열(string)은 str 형에 속하고, 정수는 int형에 속합니다. 그러나, 소수점이 있는 실수는 float라는 형에 속하는데, 실수가 부동소수점(floating-point)이라 불리는 형식으로 표현되기 때문입니다.

>>> type(3.2)
<type 'float'>

'17' 이나 '3.2' 같은 값들은 어떻게 될까요? 숫자처럼 보이지만 문자열처럼 따옴표로 둘러싸여 있습니다.

>>> type('17')
<type 'str'>
>>> type('3.2')
<type 'str'>

문자열 이네요.

굉장히 큰 정수를 입력할 때, 1,000,000처럼 숫자 세 개마다 콤마를 넣고 싶을 수 있습니다. 파이썬에서 이 것은 정수를 입력하는 올바른 방법이 아닙니다. 하지만 문법적으로는 올바릅니다:

>>> 1,000,000
(1, 0, 0)

전혀 우리가 원한 게 아니네요! 파이썬은 1,000,000 를, 콤마로 분리된 정수의 나열로 해석합니다. 이 것이 의미 오류의 첫 번 째 예입니다: 오류 메시지를 만들지 않고 잘 실행되지만, “올바른” 일을 한 것은 아닙니다.

변수

[variables]

프로그래밍 언어의 가장 강력한 기능 중 하나는 변수를 다루는 능력입니다. 변수는 값을 가리키는 이름입니다.

대입문은 새 변수를 만들고 값을 부여합니다:

>>> message = 'And now for something completely different'
>>> n = 17
>>> pi = 3.1415926535897932

이 예는 세 개의 대입을 만듭니다. 첫 번째는 message는 이름의 새 변수에 문자열을 대입합니다. 두 번째는 n 에 정수 17을 주고, 세 번째는 pi에 \(\pi\) (근사)값을 대입합니다.

종이 위에 변수를 표현할 때 널리 쓰이는 방법은, 이름을 쓴 후에 화살표로 값과 연결하는 것입니다. 이런 종류의 그림을 상태도라 부르는데, 현재 각 변수의 상태를 보여주기 때문입니다(각 값을 변수의 마음상태라고 생각해보세요). 그림 [fig.state2]은 앞에 나온 예의 결과를 보여줍니다.

state2

[fig.state2]

변수의 형은, 그 변수가 가리키는 값의 형과 같습니다.

>>> type(message)
<type 'str'>
>>> type(n)
<type 'int'>
>>> type(pi)
<type 'float'>

[연습 2.1.]

0으로 시작하는 정수를 입력하면, 잘 알아듣기 힘든 오류를 만나게 됩니다:

>>> zipcode = 02492
                  ^
SyntaxError: invalid token

다른 숫자들은 괜찮아 보입니다만, 결과가 기이합니다:

>>> zipcode = 02132
>>> zipcode
1114

어떤 일이 일어나고 있는지 짐작할 수 있나요? 힌트: 값 01, 010, 0100, 01000 를 표시해 보세요.

변수명과 예약어

프로그래머는 보통 변수에 의미 있는 이름을 붙입니다—변수의 용도를 문서화합니다.

변수명은 아무리 길어도 상관없습니다. 영문자(영어 알파벳)와 숫자를 포함할 수 있지만, 숫자로 시작할 수는 없습니다. 대문자를 사용할 수는 있지만, 소문자로 시작하는 것이 좋습니다(나중에 왜 그런지 보게 됩니다).

밑줄 문자, _, 는 이름에 나올 수 있습니다. 종종 my_name 이나 airspeed_of_unladen_swallow 처럼 여러 단어로 구성된 이름에 사용됩니다.

변수에 잘못된 이름을 지정하면, 문법 오류가 발생합니다:

>>> 76trombones = 'big parade'
SyntaxError: invalid syntax
>>> more@ = 1000000
SyntaxError: invalid syntax
>>> class = 'Advanced Theoretical Zymurgy'
SyntaxError: invalid syntax

76trombones 이 잘못된 이유는 숫자로 시작한 것입니다. more@ 는 잘못된 문자, @를 포함하고 있는 것이 문제입니다. 그런데 class는 뭐가 잘못이지요?

class는 파이썬의 예약어중 하나입니다. 인터프리터는 예약어를 프로그램의 구조를 파악하는데 사용하고, 변수명으로 사용될 수 없습니다.

파이썬 2는 31개의 예약어를 갖고 있습니다:

and       del       from      not       while
as        elif      global    or        with
assert    else      if        pass      yield
break     except    import    print
class     exec      in        raise
continue  finally   is        return
def       for       lambda    try

파이썬 3에서, exec는 더 이상 예약어가 아니지만, nonlocal은 예약어입니다.

아마 이 목록을 가까이 두고 싶을 겁니다. 인터프리터가 여러분의 변수명에 대해 불평을 하는데 이유를 모르겠다면, 이 목록에 들어있는지 확인해보세요.

연산자와 피연산자

연산자는 덧셈이나 곱셈 같은 계산을 표현하기 위해 사용하는 특별한 기호입니다. 연산자가 적용되는 값들을 피연산자라고 부릅니다.

다음 예에서 볼 수 있듯이, 연산자 +, -, , /, *는 순서대로 덧셈, 뺄셈, 곱셈, 나눗셈, 거듭제곱을 수행합니다:

20+32   hour-1   hour*60+minute   minute/60   5**2   (5+9)*(15-7)

다른 언어들에서, ^ 는 거듭제곱에 사용됩니다만, 파이썬에서는 XOR 이라고 불리는 비트 연산자입니다. 이 책에서 비트 연산자는 다루지 않습니다만, http://wiki.python.org/moin/BitwiseOperators에서 읽으실 수 있습니다.

파이썬 2 에서, 나눗셈 연산자는 여러분의 예상과 다른 결과를 줄 수 있습니다:

>>> minute = 59
>>> minute/60
0

minute 의 값은 59고, 전통적인 대수에서 59를 60으로 나누면 0이 아니라 0.98333입니다. 이런 차이가 발생하는 이유는, 파이썬이 정수 나눗셈(floor division)을 수행하고 있기 때문입니다. 두 피연산자가 모두 정수면 결과도 정수입니다; 정수 나눗셈은 소수부분을 잘라버리기 때문에, 이 예에서는 0 으로 내림 됩니다..

파이썬 3에서 나눗셈의 결과는 float입니다. 새 연산자 // 가 정수 나눗셈을 수행합니다.

두 피연산자중 어느 하나라도 실수라면, 파이썬은 실수 나눗셈을 수행하고, 결과는 float 입니다:

>>> minute/60.0
0.98333333333333328

표현식과 문장

표현식은 값, 변수, 연산자의 조합입니다. 값은 그 자체로 표현식으로 취급되고, 변수도 마찬가지 입니다. 그래서 (변수 x에 이미 값이 대입되었다고 가정하면) 다음 예들은 모두 올바른 표현식입니다:

17
x
x + 17

문장은 파이썬 인터프리터가 실행할 수 있는 단위 코드입니다. 우리는 이미 두 종류의 문장을 보았습니다: print 와 대입.

기술적으로 표현식은 문장이기도 합니다. 하지만 이 두 가지가 다르다고 생각하는 것이 아마도 더 간단할 것입니다. 중요한 차이점은, 표현식은 값을 갖지만 문장은 그렇지 않다는 것입니다.

대화형 모드와 스크립트 모드

인터프리터 언어를 쓸 때의 장점 중 하나는, 스크립트에 입력하기 전에, 대화형 모드에서 코드 조각을 실험할 수 있다는 것입니다. 그러나 대화형 모드와 스크립트 모드간에는, 혼란을 일으킬 수도 있는 차이점이 있습니다.

예를들어, 파이썬을 계산기로 사용할 때, 이렇게 입력할 수 있습니다.

>>> miles = 26.2
>>> miles * 1.61
42.182

첫 줄은 miles 에 값을 대입합니다만, 가시적인 효과는 없습니다. 두 번째 줄은 표현식이라서, 인터프리터가 계산하고 결과를 표시합니다. 그래서 우리는 마라톤이 대략 42킬로미터인 줄 알게 됩니다.

그러나 같은 코드를 스크립트에 넣고 실행하면, 아무런 출력도 얻지 못합니다. 스크립트 모드에서 표현식 만으로는 아무런 가시적인 효과를 주지 못합니다. 파이썬이 실제로 표현식을 계산하기는 합니다만, 여러분이 구체적으로 지시하지 않는 이상, 값을 표시하지는 않습니다:

miles = 26.2
print miles * 1.61

이런 차이가 처음에는 혼란스럽게 느껴질 수 있습니다.

스크립트는 보통 연속되는 문장들을 포함합니다. 만약 하나 이상의 문장이 있다면, 문장들이 실행됨에 따라 결과는 한번에 하나씩 나타납니다.

예를 들어, 스크립트

print 1
x = 2
print x

는 이런 출력을 만듭니다

1
2

대입문은 아무것도 출력하지 않습니다.

[연습 2.2.]

파이썬 인터프리터에 다음과 같은 문장들을 입력하고, 어떤 일이 일어나는지 관찰하세요:

5
x = 5
x + 1

이제 같은 문장들을 스크립트에 넣고 실행하세요. 출력은 무엇입니까? 스크립트를 수정하여 각 표현식을 print 문장으로 바꾼 후 다시 실행해 보세요.

연산의 순서

한 표현식에 여러 연산자가 등장할 때, 계산 순서는 우선규칙에 의해 결정됩니다. 수학 연산자들에 대해서 파이썬은 수학적 관례를 따릅니다. PEMDAS라는 약어는 규칙을 기억하는 좋은 방법입니다:

  • 괄호(Parentheses)는 가장 높은 우선순위를 갖고, 여러분이 원하는 순서대로 계산이 일어나도록 확실히 하는데 사용될 수 있습니다. 괄호 안에 있는 표현식이 먼서 계산되기 때문에, 2 * (3-1) 는 4이고, (1+1)**(5-2) 는 8입니다. (minute * 100) / 60 에서처럼 결과에 영향이 없더라도, 표현식을 읽기 쉽게 만들기 위해 괄호를 사용할 수도 있습니다.
  • 거듭제곱(Exponentiation)는 그 다음으로 높은 우선순위를 갖습니다. 2**1+1 는 4가 아니라 3이고, 3*1**3 는 27이 아니라 3입니다.
  • 곱셈(Multiplication)과 나눗셈(Division) 은 같은 우선순위를 갖는데, 서로 같은 운선순위를 갖는 덧셈(Addition) 과 뺄셈(Subtraction) 보다는 높습니다. 2*3-1 은 4가 아니라 5이고, 6+4/2 은 5가 아니라 8입니다.
  • 같은 우선순위를 갖는 연산자는 (거듭제곱을 제외하고) 왼쪽에서 오른쪽으로 계산됩니다. 그래서 표현식 degrees / 2 * pi에서, 나눗셈이 먼저 일어나고, 그 결과를 pi와 곱합니다. \(2 \pi\) 로 나누려면, 괄호를 사용하거나 degrees / 2 / pi처럼 써야 합니다.

저는 다른 연산자들의 우선규칙을 기억하려고 애쓰지 않습니다. 표현식을 볼 때 애매하다면, 괄호를 사용해서 명확하게 만듭니다.

문자열 연산들

일반적으로, 문자열이 숫자처럼 보인다 할지라도, 문자열을 대상으로 수학연산을 수행할 수는 없습니다. 그래서 다음 예는 올바르지 않습니다:

'2'-'1'    'eggs'/'easy'    'third'*'a charm'

+ 연산자를 문자열에 사용할 수 있습니다만, 여러분이 예상하는 것과는 다를 수 있습니다; 두 문자열의 끝을 이어 붙여 하나의 문자열로 만드는 접합이 일어납니다. 예를 들어:

first = 'throat'
second = 'warbler'
print first + second

이 프로그램은 throatwarbler 를 출력합니다.

연산자도 문자열에 사용될 수 있습니다; 반복을 수행합니다. 예를 들어, 'Spam'*3'SpamSpamSpam'입니다. 피연산자의 한쪽이 문자열이면, 다른 한쪽은 정수야 됩니다.

이런 + 와 의 용법은 덧셈과 곱셈에 대한 은유로 볼 때 말이 됩니다. 4*3 이 4+4+4인 것과 마찬가지로, 'Spam'*3'Spam'+'Spam'+'Spam' 일 것이라고 기대할 수 있고, 실제로 그렇습니다. 반면에, 문자열 접합과 반복이 정수 덧셈과 곱셈과 다르도록 만드는 중요한 방식이 있습니다. 덧셈에는 있지만 문자열 접합에는 없는 성질을 생각해낼 수 있습니까?

주석

프로그램이 크고 복잡해져 감에 따라 점점 읽기 어렵게 됩니다. 형식어는 밀도가 높고, 종종 코드 조각을 읽었을 때, 무슨 일을 하며, 왜 하는지 파악하기가 어렵습니다.

이런 이유 때문에, 프로그램에 설명을 붙여, 프로그램이 하는 일을 자연어로 기술하는 것이 좋습니다. 이 설명을 주석이라 부르고, # 기호로 시작합니다:

# 경과시간의 백분율 계산
percentage = (minute * 100) / 60

이 경우는 한 줄 전체가 주석입니다. 주석을 줄 끝에 붙일 수도 있습니다:

percentage = (minute * 100) / 60     # 시간의 백분율

# 에서부터 줄의 끝까지 등장하는 모든 것들은 무시됩니다—프로그램에 아무런 영향을 주지 않습니다.

주석은 코드의 자명하지 않은 특성들을 문서화할 때 가장 유용합니다. 코드가 무엇을 하는지 독자가 파악할 수 있을 거라는 가정은 합리적입니다; 를 설명하는 것이 더 유용합니다.

이 주석은 코드와 중복되어 쓸모 없습니다:

v = 5     # assign 5 to v

이 주석은 코드에는 없는 유용한 정보를 담고 있습니다:

v = 5     # velocity in meters/second.

좋은 변수명은 주석의 필요를 줄입니다. 하지만 긴 이름은 복잡한 표현식들을 읽기 어렵게 만들기 때문에 절충이 필요합니다.

디버깅

이 시점에서 여러분이 만들 문법에러는 잘못된 변수명 때문일 가능성이 높습니다. class 나 yield 처럼 예약어를 사용하거나, odd~jobUS$처럼 잘못된 글자를 포함하는 경우입니다.

변수명에 공백문자를 넣는다면, 파이썬은 연산자가 빠진 두 개의 피연산자라고 생각합니다:

>>> bad name = 5
SyntaxError: invalid syntax

문법 오류의 경우에, 오류 메시지는 큰 도움이 못됩니다. 가장 흔한 메시지는 SyntaxError: invalid syntax 와 SyntaxError: invalid token 인데, 둘 다 그리 많은 정보를 주지 않습니다.

가장 많이 만들 실행시간 오류는 “정의전의 사용”인데, 변수에 값을 대입하기도 전에 사용하려고 하는 경우 입니다. 이 오류는 변수명에 오타가 있을 경우에도 발생할 수 있습니다:

>>> principal = 327.68
>>> interest = principle * rate
NameError: name 'principle' is not defined

변수명은 대문자와 소문자를 다른 것으로 취급하기 때문에, LaTeX 은 latex과 같지 않습니다.

이 시점에서 가장 빈번히 일어날 의미 오류의 원인은 연산의 순서입니다. 예를 들어, \(\frac{1}{2 \pi}\) 를 계산하기 위해서 이렇게 쓰려고 할 수 있습니다:

>>> 1.0 / 2.0 * pi

그러나 나눗셈이 먼저 일어나서 \(\pi / 2\) 가 되는데, 의도한 결과가 아니지요! 파이썬이 여러분이 어떤 코드를 쓰려는 의도를 갖고 있는지 알 수 있는 방법은 없습니다. 때문에 이 경우 오류 메시지 없이, 엉터리 결과를 얻게 됩니다.

Glossary

값 value:
자료의 기본 단위 중 하나로, 프로그램이 다루는 숫자나 문자열과 같은 것.
형 type:
값의 유형. 지금까지 우리가 살펴본 형은 정수(int), 실수(float), 문자열(str)입니다.
정수 integer:
정수를 표현하는 형.
실수 floating-point:
소수부를 포함하는 수를 표현하는 형.
문자열 string:
연속된 문자를 표현하는 형.
변수 variable:
값을 가리키는 이름.
문장 statement:
명령이나 동작을 표현하는 코드의 한 영역. 지금까지 본 문장은 대입문과 print 문이다.
대입문 assignment:
값을 변수에 대입하는 문장.
상태도 state diagram:
변수의 집합과 그들이 가리키는 값의 도식화.
예약어 keyword:
프로그램을 해석하기 위해 컴파일러가 사용하도록 예약된 단어. if, def, while 와 같은 예약어들은 변수명으로 사용될 수 없다.
연산자 operator:
덧셈, 곱셈, 문자열 접합등과 같은 간단한 계산을 표현하는데 사용되는 특수한 기호.
피연산자 operand:
연산자가 작용하는 대상.
정수 나눗셈 floor division:
몫의 내림을 취하여 소수부분을 제거하는 나눗셈.
표현식 expression:
단일한 결과를 표현하기 위해 변수, 연산자, 값을 조합하는 식.
계산하기 evaluate:
단일한 값으로 환원하기 위해 연산을 수행하여 표현식을 단순화하는 작업.
우선규칙 rules of precedence:
여러개의 연산자와 피연산자들로 구성된 표현식의 계산 순서를 규정하기 위한 규칙.
접합 concatenate:
두 개의 피연산자 끝을 이어 붙여 합치는 작업.
주석 comment:
다른 프로그래머(또는 원시 코드를 읽는 아무나)에게 전달할 목적으로 제공될 뿐, 프로그램의 실행에는 영향을 주지 않는 정보.

연습

[연습 2.3.]

다음과 같은 대입문들을 실행한다고 가정하세요:

width = 17
height = 12.0
delimiter = '.'

다음에 오는 각각의 표현식들에 대해, 표현식의 값과 (표현식의 값이 갖는) 형을 써보세요..

  1. width/2
  2. width/2.0
  3. height/3
  4. 1 + 2 * 5
  5. delimiter * 5

파이썬 인터프리터를 사용하여 답을 확인해 보세요.

[연습 2.4.]

파이썬 인터프리터를 계산기로 쓰는 연습을 해보세요:

  1. 반지름이 \(r\) 인 구의 부피는 \(\frac{4}{3} \pi r^3\) 입니다. 반지름이 5인 구의 부피는 무엇입니까? 힌트: 392.7은 아닙니다!
  2. 책의 정가는 $24.95 지만, 서점이 40% 의 할인을 받는다고 가정하세요. 첫 권의 운송료는 $3 이고, 이 이후는 권당 75 센트입니다. 60권의 도매가는 얼마일까요?
  3. 제가 오전 6:52 에 집을 떠나서, 가볍게 (마일당 8:15) 1마일을 뛰고, 좀 빨리 (마일당 7:12) 3마일을 뛴 후, 다시 가볍게 1마일을 마저 달린다면, 아침 식사를 위해 집에 돌아오는 시간은 언제 일까요?