함수 속에 정의된 변수들은 함수가 호출되어 실행될 때 만들어지고, 함수의 실행이 끝날 때 삭제된다. 그래서 함수 속에서만 사용할 수 있고, 함수 밖에서 부르는 것은 불가능하다. 이렇게 함수 속에서 만들어졌다가 사라지는 변수를 지역변수(local variable)라고 하며, 함수 속에서 변수가 정의되는 공간을 ‘지역 이름공간(local namespace)’이라고 한다. 반대로 함수 밖, 프로그램의 전체 영역(전역 이름공간)에서 정의되는 변수는 전역변수(global-)라고 부른다. 전역변수는 프로그램 전체에서 공유되며, 함수 안에서도 부를 수 있다.

3.4.1 지역변수는 함수만의 것

각 함수의 지역변수는 그 함수 자신만이 사용할 수 있으며, 함수 밖에서는 사용할 수 없다. 함수 안에서 정의된 모든 변수는 지역변수다. 매개변수도 함수 안에 정의되므로 지역변수다. 다음 예제에서 전역변수와 지역변수를 구별해 보자.

코드 3-11 전역변수와 지역변수가 함께 사용된 프로그램

seconds_per_minute = 60  # 1분은 60초

def minutes_to_seconds(minutes):
    """분을 입력받아 같은 시간만큼의 초를 반환한다."""
    seconds = minutes * seconds_per_minute
    return seconds

print(minutes_to_seconds(3))  # 화면에 180이 출력된다
print(seconds)     # 지역변수를 함수 밖에서 읽는 오류

실행 결과:

180
NameError: name 'seconds' is not defined

위 예에서 1분이 몇 초인지 나타내는 seconds_per_minute 변수는 한번 정의되었으면 프로그램 어디에서든 사용할 수 있는 전역변수다. minutes_to_seconds() 함수 안에서도 이 변수를 읽고 있다. 반대로 분을 입력받는 매개변수 minutes와 함수 안에서 중간 계산 결과를 저장하는 변수 seconds는 지역변수다. 마지막 행에서처럼 함수 밖에서 이런 변수를 읽으려고 하면 함수 밖에는 그 변수가 없기 때문에 이름 오류가 발생한다.

지역변수는 그 지역변수를 가진 함수만의 것이고, 함수 밖에서는 사용할 수 없다. 반면에 전역변수는 프로그램 어디에서든 사용할 수 있으므로 함수 안에서도 읽을 수 있다. 단, 함수 안에서 전역변수에 새로운 값을 대입하는 것은 금지된다. (잠시후 설명할 global 문을 사용하면 예외적으로 가능해진다.) 표 3-1은 이 규칙을 표로 정리한 것이다.

특징 전역변수 지역변수
함수 안에서 읽기 가능 가능
함수 안에서 수정 불가(*) 가능
함수 밖에서 읽기 가능 불가
함수 밖에서 수정 가능 불가

표 3-1 전역변수와 지역변수의 접근 조건 (*: global 문 사용시 가능)

개념 정리

  • 전역변수: 함수 밖, 전역 이름공간에 정의된 변수
  • 지역변수: 함수 안, 지역 이름공간에 정의된 변수
  • 지역변수는 그 변수가 정의된 함수 안에서만 읽을 수 있다.
  • 전역변수는 프로그램 어디서든 읽을 수 있다. 단, 함수 안에서 전역변수에 새로운 값을 대입할 수는 없다.

연습문제

연습문제 3-9 전역변수와 지역변수 구별하기

다음 프로그램에서 사용된 전역변수와 지역변수를 각각 나열해 보아라. 각 지역변수가 어느 함수에 속하는지도 말해 보아라.

pi = 3.141592653589793

def area_of_circle(radius):
    """원의 반지름(radius)을 입력받아 넓이를 반환한다."""
    area = radius * radius * pi
    return area

def volumn_of_cylinder(radius, height):
    """원기둥의 반지름(radius)과 높이(height)를 입력받아
    부피를 반환한다."""
    top_area = area_of_circle(radius)
    volumn = top_area * height
    return volumn

result = volumn_of_cylinder(5, 10)
print(result)

3.4.2 지역변수의 생존 기간

지역변수는 함수가 실행될 때마다 새로 만들어지고, 함수의 실행이 종료되면 삭제된다. 코드 3-11을 예로 들면, minutes_to_seconds() 함수의 실행이 종료된 후에는 seconds라는 변수가 더이상 존재하지 않는다. 함수 밖에서 지역변수를 사용할 수 없는 것은 이 이유 때문이기도 하다. 함수가 실행중일 때만 지역변수가 존재하는데, 어떻게 존재하지 않는 변수를 사용할 수 있겠는가?

이 점은 함수를 실행할 때마다 함수가 이전에 계산했던 내용을 다 잊어버린 채 새로 실행된다는 뜻이기도 하다. 함수의 이전 실행 결과를 기억해야 한다면 함수의 밖에서 결과를 보관해주어야 한다. 함수의 실행 결과를 기억하려면 결과 = 함수()와 같이 함수를 호출하는 곳에서 함수의 실행 결과를 변수에 대입하면 된다.

3.4.3 관심의 분리

모든 변수를 전역변수로 취급하여 어디서든 데이터를 자유롭게 읽고 수정할 수 있으면 편리하지 않을까? 왜 불편하게 전역변수와 지역변수를 구분하는 것일까?

문제를 나누어 해결할 때는 각각의 문제를 다룰 때 다른 문제를 배제하고 하나의 문제만을 생각할 수 있어야 한다. 함수는 프로그램 전체를 구성하는 일부이지만, 각각의 함수의 관점에서는 자기가 맡은 작은 문제만을 해결할 뿐이다. 그렇기 때문에 함수는 자기 문제에 필요한 데이터만을 조작해야 하고 관련이 없는 프로그램 전체 데이터나 다른 함수의 데이터에는 참견하지 않아야 한다.

전역변수는 프로그램 전체에서 공통적으로 사용되고 잘 변하지 않는 데이터로 제한해야 한다. 전역변수가 수시로 변하고 여러 함수에서 손댄다면 프로그래머가 프로그램의 흐름을 파악하기 어렵다. 함수의 입력 통로와 출력 통로를 매개변수와 return 문으로 제한하는 것도 프로그램의 흐름을 파악하기 쉽게 하기 위한 것이다. 함수 중간에서 함수 밖의 데이터를 건드리는 것은 중간에 구멍이 나서 물이 새는 파이프와 같다. 그 물이 어디로 흐를 지 예측하기란 쉽지 않다.

전역변수를 함수 안에서 수정하는 것이 좋지 않다는 것을 염두에 두고, 이어지는 내용을 학습하자.

개념 정리

  • 지역변수를 이용해 그 데이터와 관련된 문제를 함수 내부의 문제로 국한시킬 수 있다.
  • 전역변수를 함수 안에서 수정하는 것은 좋지 않다.

3.4.4 함수 안에서 전역변수 수정하기

함수 안에서 전역변수를 수정할 수 없다는 규칙은 global 문을 이용해 깰 수 있다. global 문이 필요한 상황과 global 문을 사용하는 방법을 알아보자.

함수의 실행 결과 누적하기

함수가 전역변수를 수정해야 하는 대표적인 상황으로 함수의 실행 결과를 누적해야 하는 경우가 있다. 실행될 때마다 데이터를 누적하는 함수의 예로, 자기가 실행된 횟수를 전역변수에 기록하며 화면에 출력하는 count() 함수를 작성해 보았다.

코드 3-12 함수 안에서 전역변수를 수정하려 하는 오류

times = 0  # count 함수가 실행된 횟수

def count():
    """이 함수가 실행된 횟수를 화면에 출력한다."""
    times = times + 1  # 여기서 오류가 발생한다
    print(times)

실행 결과:

1
UnboundLocalError: local variable 'times' referenced before assignment

위 코드의 count() 함수는 전역변수 times를 수정하려 하지만 오류가 발생한다. 오류가 발생하는 과정을 생각해 보자. 함수 입장에서는 함수 안의 변수는 지역변수이기 때문에 times = 값 을 대입할 때 times라는 새로운 지역변수를 생성한다. 그런데 times에 대입할 값이 공교롭게도 times + 1이므로, 아직 만들어지지 않은 변수를 읽으려는 셈이 된다. 원래 의도는 전역변수 times의 값을 읽으려는 것이지만, times에 무언가를 대입하려 하고 있으므로(함수 안에서는 지역변수에만 그렇게 할 수 있다) 파이썬은 times라는 이름을 새로 만들 지역변수로 생각하는 것이다.

이 문제를 해결하려면 global 문을 이용해 times 변수가 전역변수를 가리키는 것임을 명시적으로 밝혀야 한다.

global 문

global 문을 사용하면 함수 안에서도 전역변수의 값을 수정할 수 있다. 함수 안에서 global 변수이름 명령을 실행하면 그 이름은 전역변수를 가리키게 되고, 전역변수의 값을 수정하는 것도 가능해진다. count() 함수에 global 문을 삽입해 수정해 보자.

코드 3-13 global 문의 사용

times = 0   # count 함수가 실행된 횟수

def count():
    """이 함수가 실행된 횟수를 화면에 출력한다."""
    global times  # 'times는 전역변수다' 라는 의미
    times = times + 1
    print(times)

count()  # 화면에 1이 출력된다
count()  # 화면에 2가 출력된다

실행 결과:

1
2

위 코드에서는 global 문을 사용하여 count() 함수 안에서도 함수 밖의 전역변수 times를 수정할 수 있다. 원래 의도한 함수를 만드는 데 성공했다.

global 문은 사용하지 않는 것이 좋다

global 문에 대해 배웠지만 이 명령은 사용하지 않는 것이 좋다. global 문을 사용한다는 것은 함수가 매개변수와 반환값을 이용해 외부와 소통하는 자연스러운 흐름을 깨트리는 일이다.

함수 안에서 전역변수를 수정하지 않고, 매개변수와 반환값만 이용하더라도 함수의 실행 결과를 누적하는 데 부족함이 없다. 다음 예제는 전역변수를 직접 수정하는 대신, 매개변수와 반환값을 이용하도록 count() 함수를 수정한 버전이다.

코드 3-14 매개변수와 반환을 이용한 count() 함수

times = 0   # count 함수가 실행된 횟수

def count(times):
    """이 함수가 실행된 횟수를 화면에 출력한다."""
    times = times + 1
    print(times)
    return times

times = count(times)  # 화면에 1이 출력된다
times = count(times)  # 화면에 2가 출력된다

실행 결과:

1
2

위 코드에는 두 개의 times 변수가 등장한다. 하나는 전역변수 times이고, 다른 하나는 count() 함수의 지역변수이자 매개변수인 times다. 두 변수는 이름은 같지만 (이름을 다르게 지어도 된다) 존재하는 공간이 다르기 때문에, 서로 전혀 다른 변수다. count() 함수는 자신의 지역변수인 times만을 수정하고, 전역변수 times는 건드리지 않는다. 전역변수 times는 함수 밖에서만 수정되고 있다.

이처럼 함수 안에서 전역변수를 수정하지 않아도 함수의 실행 결과를 누적할 수 있다. global 문은 필수가 아닐 뿐더러 많이 사용할수록 프로그램이 복잡해지기 쉽다. 다른 사람의 프로그램을 읽을 수 있도록 global 문의 사용법을 알아두되, 가급적이면 사용하지 않도록 하자.