이번 절에서는 선택과 반복을 수행하는 다른 방법을 알아 본다. 이 절의 내용은 알아두면 좋지만 필수는 아니다. 어렵게 느껴진다면 건너뛰어도 괜찮다.

6.3.1 단축 평가

단축 평가(short-circuit evaluation)란 계산을 진행하는 도중 결과를 이미 알았을 때 나머지 계산 과정을 생략하는 것이다. 불리언 연산 and 와 or 는 특성상 단축 평가가 가능하다. 컴퓨터의 계산 자원을 절약하기 위해 파이썬을 포함한 대부분의 프로그래밍 언어는 이 두 연산에 대한 단축 평가를 수행한다.

and 연산은 좌변과 우변이 모두 참이어야 참으로 평가된다. 만일 좌변이 거짓이라면, 우변은 살펴볼 볼 필요도 없이 거짓이다.

코드 6-39 and 연산의 단축 평가

(1 == 2) and (3 < 4) and (True != False)

위 코드의 (1 == 2)는 거짓이므로, (3 < 4) and (True != False)는 평가해 보지 않아도 거짓임을 판단할 수 있다. 파이썬은 (1 == 2)만을 평가한 후 거짓임을 알고 나머지는 계산하지 않은 채 거짓으로 결론내린다.

반대로 or 연산은 좌변과 우변이 모두 거짓이어야 거짓으로 평가된다. 만일 좌변이 참이라면, 우변의 계산을 생략할 수 있다.

코드 6-40 or 연산의 단축 평가

('red' in ['red', 'violet', 'blue']) or (1 == 10 - 9) and (20 < 20)

위 코드의 ('red' in ['red', 'violet', 'blue'])은 참이다. 파이썬은 or 우변의 연산을 생략하고 전체 식의 결과를 참으로 결론내린다.

단축 평가를 이용한 선택

and 연산과 or 연산의 단축 평가 특성을 이용하면 if 문을 사용하지 않고도 선택을 나타낼 수 있다.

코드 6-41 and 와 or 를 이용한 선택

def 홀짝(n):
    """n이 홀수인지 짝수인지를 화면에 출력한다."""
    (n % 2 == 0) and print(n, '은 짝수입니다.')
    (n % 2 == 0) or print(n, '은 홀수입니다.')

홀짝(10)
홀짝(11)

실행 결과:

10 은 짝수입니다.
11 은 홀수입니다.

코드 6-41의 홀짝()함수는 n이 짝수일 때 and 연산과 or 연산의 좌변은 참이 된다. and 연산에서는 우변도 참인지 확인하기 위해 코드가 실행되지만, or 연산에서는 우변이 참인지 확인할 필요가 없어 코드가 실행되지 않는다. n이 홀수일 때는 그 반대다.

단축 평가를 if 문 대신 사용하면 코드가 간결해지지만 익숙하지 않은 사람은 헷갈리기 쉽다는 문제가 있다. 단축 평가보다는 if 문을 사용하는 것이 좋지만, 프로그래머 중에는 단축 평가를 즐겨 사용하는 사람도 있으므로 읽는 법은 알아두는 것이 좋다.

6.3.2 재귀

내가 내 이름을 부를 수 있는 것처럼, 함수는 자기 자신을 호출할 수 있다. 함수가 자신을 직접 또는 간접적으로 호출하는 것을 재귀(recursion)라고 한다. 재귀를 사용하면 while 문이나 for 문 없이도 반복 작업을 수행할 수 있다. 다음은 전형적인 재귀 함수의 예다.

코드 6-42 n 이상 m 미만의 자연수 출력

def 자연수(n, m):
    """수 n을 출력하고, 1 더한 수가 m보다 작으면 그 수도 출력한다."""
    print(n)
    if n + 1 < m:
        자연수(n + 1, m)

자연수(4, 8)

실행 결과:

4
5
6
7

코드 6-42의 자연수() 함수는 자연수 n과 m을 입력받아, 그 사이의 모든 자연수를 출력하는 함수다. 이 함수는 본문 안에서 자기자신(자연수())을 호출한다. 이 함수가 실행되는 과정을 생각해 보자.

  1. 처음에 자연수(4, 8)을 호출. 화면에 4가 출력된다. 4 + 1 < 5는 참이므로, 자연수(4 + 1, 8)이 재귀 호출된다.
  2. 다시 호출된 자연수()함수는 전달받은 수 n을 출력하고, 조건을 검사해 자연수(n + 1, m)을 다시 호출한다.
  3. 자연수() 함수는 계속 동일한 형태로 호출되지만, 이 함수의 매개변수 n에 전달되는 값은 계속 변한다. n + 1 < m 이라는 조건이 거짓이 된 호출에서 재귀 호출은 더이상 일어나지 않는다.

이처럼 재귀 함수는 while 문이나 for 문과 형태는 다르지만 그들과 마찬가지로 반복의 시작, 각 주기의 변화, 반복의 유지 조건(종료 조건)이 있다. 재귀는 어렵다는 편견이 있지만 언제 자기 자신을 호출할 것인지, 어떤 값을 변화시켜 매개변수로 전달할 것인지에 잘 신경쓰면 그리 어렵지 않다.

재귀 활용하기

피보나치 수열은 1, 1, 2, 3, 5, 8, … 순으로 앞의 두 수를 합한 수를 나열한 수열이다. 예를 들어 다섯 번째 피보나치 수는 세 번째 수 2와 네 번째 수 3의 합이다. 다음 세 가지 규칙만 알면 몇 번째 피보나치 수라도 계산할 수 있다.

  • 규칙 1: 첫 번째 피보나치 수는 1
  • 규칙 2: 두 번째 피보나치 수는 1
  • 규칙 3: 그 후, N 번째 피보나치 수는 (N - 1) 번째 피보나치 수와 (N - 2) 번째 피보나치 수의 합

규칙 3에서 피보나치 수를 구하는 방법 자신이 피보나치 수를 구하는 방법을 사용하고 있다. 이 방법처럼 문제 해결 방법이 그 자신을 이용하는 경우가 있다. 재귀를 활용하면 이런 문제 해결법을 그대로 프로그램으로 옮길 수 있다. 다음 코드는 피보나치 수를 구하는 규칙을 그대로 재귀 함수로 옮긴 것이다. 파이참에서 fibonacci.py 파일을 만들고 다음 코드를 입력하여 실행해 보자.

코드 6-43 재귀를 이용한 피보나치 수 계산 (fibonacci.py)

def n번째_피보나치_수(n):
    """n번째 피보나치 수를 반환한다."""
    if n == 1:     # 첫 번째 피보나치 수는 1
        return 1
    elif n == 2:   # 두 번째 피보나치 수는 1
        return 1
    else:          # 그 후의 피보나치 수
        return n번째_피보나치_수(n - 1) + n번째_피보나치_수(n - 2)

# 1번째부터 11번째 피보나치 수까지 출력
for n range(1, 12):
    print(n번째_피보나치_수(n), end=' ')

실행 결과:

1
1
2
3
5
8
13
21
34
55
89

재귀의 단점

재귀는 특정한 문제를 해결할 때 편리하지만 단점도 있다. 재귀는 함수의 실행이 끝나기도 전에 또 다시 함수를 실행하기 때문에, 재귀 호출이 연달아 많이 일어나면 컴퓨터의 계산 자원과 기억 자원을 빠르게 소모할 수 있다. 코드 6-42에서 정의한 자연수() 함수를 이용해 1에서 1천까지의 자연수를 출력하도록 해 보면 다음과 같이 오류가 발생한다.

코드 6-44 재귀 호출이 여러 번 누적되면 오류가 발생한다

>>> 자연수(1, 1000)
1
2
(중략)
995
996
RecursionError: maximum recursion depth exceeded while calling a Python object

마지막의 오류 메시지를 해석하면 “재귀 오류: 파이썬 객체를 호출하는 도중 최대 재귀 깊이를 초과했습니다.” 즉 파이썬에서 제한된 최대 재귀 호출 단계를 넘어서서 오류가 발생했다는 뜻이다. 재귀를 무한정 허용하면 컴퓨터의 자원을 다 써버릴 수 있기 때문에 파이썬에서는 재귀의 호출을 일정 수준에서 제한해 두었다.

프로그래밍 언어마다 문화의 차이가 있다. 몇몇 프로그래밍 언어(리스프가 대표적)는 while 문이나 for 문 같은 특별한 반복 기능보다는 재귀를 활용해 반복적인 문제를 해결하는 것을 권장하며, 재귀의 한계를 개선하기 위한 기능을 제공하기도 한다. 그러나 파이썬을 포함한 대부분의 프로그래밍 언어에서는 재귀보다 while 문이나 for 문을 활용하는 것이 권장된다. 파이썬에서 프로그램을 작성할 때는 가급적 while 문이나 for 문을 사용하는 편이 좋고, 재귀는 꼭 필요할 때만 주의깊게 사용해야 한다.