10.1.1 모듈: 프로그램을 구성하는 독립적인 기능

모듈의 의미

모듈은 프로그램을 구성하는 기능 중에서 독립적으로 구별할 수 있는 것을 모아 분리해 둔 것이다. 프로그램을 모듈로 나누면, 많은 기능을 담은 프로그램이라도 만들고 관리하기가 수월해진다. 그리고 나누어 둔 모듈은 그 모듈의 기능이 필요한 다른 프로그램을 만들 때 재사용할 수도 있다.

파이썬에서는 소스 코드 파일 하나를 가리킬 때 ‘모듈’이라고도 부른다. 소스 코드 파일 하나에는 기능적으로 서로 연관된 데이터, 클래스, 함수만을 모아두는 것이 당연하기 때문이다.

개념 정리

  • 모듈: 전체 프로그램을 구성하는 독립적인 기능을 묶은 요소. 파이썬 소스 코드 파일 하나.

모듈로 구성된 프로그램

어떤 게임 프로그램에 다음 세 기능이 구현되어 있다고 하자.

  • 화면 출력
  • 사용자 입력 처리
  • 데이터 모델의 정의와 조작

이 기능들은 하나의 게임을 구성하는 요소다. 하지만 하는 일이 서로 구별되기도 한다. 이것을 모두 하나의 파이썬 파일에 작성해 놓으면 파일 하나에 너무 많은 기능이 들어 있어 관리하기가 어려워진다. 그럴 때는 다음과 같이 여러 개의 모듈(파이썬 소스 코드 파일)로 나누어 좀 더 쉽게 관리할 수 있다.

그림 10-1 게임 프로그램을 구성하는 모듈

그림 10-1 게임 프로그램을 구성하는 모듈

만들어 둔 모듈은 다른 프로그램을 만들 때 재사용할 수 있다. 예를 들어 그림 그리는 프로그램을 만든다면, 게임 프로그램의 ‘화면 출력’ 기능 모듈을 가져와 사용할 수 있을 것이다.

그림 10-2 모듈 재사용

그림 10-2 모듈 재사용

10.1.2 모듈 임포트하기

프로그램을 여러 개의 모듈로 나누어 작성하려면, 나누어 놓은 모듈을 읽어들여 사용하는 기능이 필요하다. 이 기능을 임포트(import)라고 한다. 모듈 임포트는 import 문으로 수행하며, 다음 양식으로 작성한다.

import 모듈이름

모듈은 객체(클래스)처럼 일종의 이름공간으로 기능한다. 객체(클래스)에 정의된 속성을 객체.속성 표현으로 가리킬 수 있는 것처럼, 모듈 안에서 전역 이름으로 정의된 데이터, 함수, 클래스 등의 객체를 모듈.이름 표현으로 가리킬 수 있다. 다음은 math 모듈을 임포트한 후 math 모듈의 객체를 읽거나 호출하는 예다.

코드 10-1 모듈 임포트하고 모듈 속의 객체 가리키기

>>> import math     # math 모듈 임포트
>>> math.pi         # math 모듈에 정의된 pi 객체 읽기
3.141592653589793

>>> math.sqrt(9)    # math 모듈에 정의된 sqrt 함수 호출
3.0

모듈을 임포트하면 모듈의 이름과 동일한 변수에 모듈이 대입된다. 변수의 이름을 직접 지정하고 싶다면 import 문에 as 이름을 붙일 수 있다. 즉, 다음과 같은 양식으로 import 문을 실행한다.

import 모듈이름 as 이름

다음은 math 모듈을 M이라는 이름으로 임포트하는 예다.

코드 10-2 모듈을 대입할 변수 이름 지정하기

>>> import math as M   # math 모듈을 임포트하여 M 변수에 대입
>>> M.pi               # 지정한 이름으로 모듈을 가리킬 수 있다
3.141592653589793

모듈의 일부 구성요소만 임포트하기

모듈에서 자주 사용하는 객체가 있다면 대입문을 활용해 별칭을 붙여 두자. 모듈 이름을 붙이지 않고 객체를 가리킬 수 있어 편리하다.

코드 10-3 모듈 속 객체에 별칭 붙이기

>>> import math
>>> root = math.sqrt  # 모듈 속 객체에 별칭을 붙여 두면,
>>> root(9)           # 모듈 이름을 붙이지 않고 가리킬 수 있다
3.0

모듈에서 어느 한 객체만이 필요하다면, 모듈을 임포트할 때부터 모듈 전체가 아닌 일부 객체만을 변수에 대입할 수 있다. import 문에 from 예약어를 붙인 다음 양식을 이용한다.

from 모듈이름 import 객체 as 이름

‘from’은 ‘~에서’라는 뜻이며, 위 양식은 ‘모듈에서 객체를 이 이름으로 임포트하라’라는 뜻이다. as 이름은 생략 가능하다. 다음은 math 모듈에서 pisqrt를 임포트하는 예다.

코드 10-4 모듈의 일부 객체만을 변수에 대입하기

>>> from math import pi            # math 모듈의 pi 객체를 임포트
>>> from math import sqrt as root  # sqrt 객체를 root 이름으로 임포트
>>> pi
3.141592653589793

>>> root(9)
3.141592653589793

이 경우, math 모듈이 아니라 모듈 속의 pi 객체와 sqrt 객체만이 변수에 대입된다. 따라서 math 모듈은 가리킬 수 없다.

개념 정리

  • import 문: 다른 모듈 또는 다른 모듈의 구성요소를 현재 모듈에서 사용할 수 있도록 임포트한다.

10.1.3 모듈 만들기

모듈을 만들 때는 어떻게 할까? 여러분은 예제를 따라 입력하거나 연습문제를 풀면서 모듈을 여러 번 만들어 봤다. 파이썬 소스 코드 파일이 바로 모듈이기 때문이다.

모듈은 처음부터 계획해 만들 수도 있고, 프로그램에서 분리해 만들 수도 있다. 전자는 “이 프로그램에는 A, B, C 기능이 필요할 것 같군. A, B, C 모듈과 이 모듈들을 사용하는 D 모듈을 만들자.”하는 방식이고, 후자는 “A 프로그램은 너무 많은 기능을 한꺼번에 처리하는데? B, C, D 모듈로 기능을 나누자.” 또는 “A 프로그램의 B 기능은 다른 곳에서도 유용하게 쓸 수 있겠는데? 모듈로 만들어 두자.”하는 방식이다.

프로그램을 하나 만든 뒤에 여러 모듈로 분리한다고 해 보자. 다음은 어떤 반의 학생 기록을 계산하는 프로그램을 정의한 것이다. 구현해 둔 함수가 조금 어렵게 느껴질 수 있지만, 통계를 구하는 함수를 세 개 정의한 뒤 함수로 데이터를 계산했다는 수준으로 이해하며 살펴보자.

코드 10-5 학생 정보를 계산하는 프로그램 (students.py)

def mean(seq):
    """시퀀스의 산술평균을 구한다."""
    return sum(seq) / len(seq)

def median(seq):
    """시퀀스의 중앙값을 구한다."""
    n = len(seq)              # 시퀀스의 길이와
    sorted_seq = sorted(seq)  # 정렬된 시퀀스를 구한 후
    
    # 시퀀스 길이가 짝수인 경우: 가운데 두 원소의 평균 반환
    if n % 2 == 0:
        return (sorted_seq[n // 2 - 1] + sorted_seq[n // 2]) / 2
    # 그 외: 가운데 원소 반환
    else:
        return sorted_seq[n // 2]

def most_frequent(seq):
    """시퀀스의 최빈값을 구한다."""
    # 사전에 빈도 기록
    frequencies = {}
    for element in seq:
        if element in frequencies:
            frequencies[element] += 1
        else:
            frequencies[element] = 1
    
    # 사전의 항목들을 빈도순으로 정렬하고 최빈값 반환
    frequencies = sorted(frequencies.items(), key=lambda item: item[1])
    return frequencies[-1][0]

# 기말고사 성적
scores = [99, 98, 96, 100, 99, 92, 93, 27, 96, 85, 100, 99]

# 학생들의 키
heights = [155.8, 182.7, 166.3, 181.1, 179.5, 173.2, 174.5, 162.3]

# 칭찬도장 받은 학생
compliments = ['박연오', '김파이', '김파이', '박연오', '박연오']

print('기말고사 성적 평균:', mean(scores))
print('가운데 앉은 학생의 키:', median(heights))
print('칭찬을 가장 많이 받은 학생:', most_frequent(compliments))

실행 결과:

기말고사 성적 평균: 90.33333333333333
학생들의 키: 173.85
칭찬을 가장 많이 받은 학생: 박연오

그런데 산술평균, 중앙값, 최빈값을 구하는 함수는 모듈로 분리해 다른 프로그램에서도 사용하면 좋을 것 같다. 함수를 소스 코드에서 잘라내고, 파이참에서 새 파일을 만들어 붙여넣으면 된다. 파일 이름이 곧 모듈명이 되므로, 새로 만들 파일의 이름을 잘 지어야 한다. 모듈의 내용이 시퀀스를 계산하는 함수들이니 stats.py라고 파일 이름을 짓자.

다음은 통계 관련 함수를 잘라내 새로 만든 모듈이다. 데이터의 정의와 함수를 호출하고 출력하는 기능을 제외했을 뿐, 함수 정의를 그대로 가져왔다.

코드 10-6 분리한 모듈 (stats.py)

def mean(seq):
    """시퀀스의 산술평균을 구한다."""
    return sum(seq) / len(seq)

def median(seq):
    """시퀀스의 중앙값을 구한다."""
    n = len(seq)              # 시퀀스의 길이와
    sorted_seq = sorted(seq)  # 정렬된 시퀀스를 구한 후
    
    # 시퀀스 길이가 짝수인 경우: 가운데 두 원소의 평균 반환
    if n % 2 == 0:
        return (sorted_seq[n // 2 - 1] + sorted_seq[n // 2]) / 2
    # 그 외: 가운데 원소 반환
    else:
        return sorted_seq[n // 2]

def most_frequent(seq):
    """시퀀스의 최빈값을 구한다."""
    # 사전에 빈도 기록
    frequencies = {}
    for element in seq:
        if element in frequencies:
            frequencies[element] += 1
        else:
            frequencies[element] = 1
    
    # 사전의 항목들을 빈도순으로 정렬하고 최빈값 반환
    frequencies = sorted(frequencies.items(), key=lambda item: item[1])
    return frequencies[-1][0]

코드 10-5에서 통계 모듈을 잘라내고 남은 부분은 이제 올바르게 실행되지 않는다. 정의되지 않은 함수를 호출하려 하기 때문이다. 통계 함수를 사용하려면 import stats 명령으로 새로 정의한 stats 모듈을 임포트해야 한다. 또한, stats.mean()과 같이 각 함수를 가리킬 때 모듈명을 명시해주어야 한다.

통계 관련 함수를 잘라내 새로 만든 모듈의 코드는 다음과 같다. 데이터의 정의와 함수를 호출하고 출력하는 기능을 제외했을 뿐, 함수 정의를 그대로 가져왔다.

코드 10-7 stats 모듈을 사용하는 프로그램

import stats    # stats 모듈(stats.py 파일)을 임포트한다

# 기말고사 성적
scores = [99, 98, 96, 100, 99, 92, 93, 27, 96, 85, 100, 99]

# 학생들의 키
heights = [155.8, 182.7, 166.3, 181.1, 179.5, 173.2, 174.5, 162.3]

# 칭찬도장 받은 학생
compliments = ['박연오', '김파이', '김파이', '박연오', '박연오']

print('기말고사 성적 평균:', stats.mean(scores))
print('가운데 앉은 학생의 키:', stats.median(heights))
print('칭찬을 가장 많이 받은 학생:', stats.most_frequent(compliments))

실행 결과:

기말고사 성적 평균: 90.33333333333333
학생들의 키: 173.85
칭찬을 가장 많이 받은 학생: 박연오

코드 10-7을 실행해보면 결과가 올바르게 출력된다. 이제 다른 프로그램을 만들더라도 stats 모듈을 임포트하여 필요할 때마다 사용할 수 있게 되었다.

10.1.4 최상위 모듈

프로그램 실행의 시작점이 되는 모듈을 ‘최상위(top-level) 모듈’이라고 한다. 예를 들어, application.py라는 파이썬 프로그램을 실행한다면 실행된 application.py 파일이 최상위 모듈이 된다. 만약 이 프로그램에서 다른 모듈을 임포트한다면, 그 모듈들은 상대적으로 하위 모듈이며 최상위 모듈이 아니다.

모든 모듈은 최상위 모듈로서 실행될 수 있다. 만약 어떤 모듈을 최상위 모듈로서 직접 실행될 때와 하위 모듈로 임포트될 때의 동작을 다르게 하고 싶다면, 그 모듈이 최상위 모듈인지 아닌지를 확인할 필요가 있다.

이는 모듈에 자동으로 정의되는 __name__ 전역 변수를 읽어 확인할 수 있다. 이 변수의 값은 모듈이 최상위 모듈로 실행된 경우에는 '__main__'이 되고, 하위 모듈로 임포트된 경우에는 그 모듈의 이름이 된다. 이를 활용하면 다음과 같이 임포트 여부를 확인하여 실행되는 코드를 다르게 할 수도 있다.

코드 10-8 자신이 최상위 모듈인지 식별하기

if __name__ == '__main__':
    print('이 모듈을 직접 실행하셨군요.')
else:
    print('이 모듈을 임포트하셨군요.')

하지만 실제로는 최상위 모듈을 검사해야 하는 경우는 많지 않다. 한 파이썬 모듈 안의 내용을 여러가지 목적을 갖는 코드로 뒤섞기보다는, 임포트하기 위한 모듈과 직접 실행하기 위한 모듈을 분명히 구별하여 프로그램을 작성하는 것이 더 바람직하기 때문이다. 실제로 사용하기보다는, 남의 코드를 읽을 때 의미를 알기 위해 알아두는 정도로 충분하다.

개념 정리

  • 최상위 모듈: 파이썬 프로그램의 실행 시작점
  • 모듈의 이름은 __name__ 전역 변수에 정의되어 있다. 최상위 모듈의 이름은 항상 '__main__'이다.