10.1.1 모듈이란

일반적 용어로 모듈은 기계나 건축물의 전체를 구성하는 독립적인 개체, 쉽게 말해 부품을 의미한다. 모듈식 생산은 모듈을 각기 생산해 조립하는 방식으로, 복잡한 제품을 좀더 쉽게 생산할 있도록 한다. 동일한 모듈 공정을 여러 제품에 활용할 수 있어 생산비용도 줄어든다.

프로그래밍의 모듈도 비슷한 개념이다. 전체 프로그램을 한꺼번에 정의하고 관리하기는 너무 복잡하다. 그래서 프로그래머들은 프로그램을 구성하는 독립적인 단위를 각각 정의하고 관리한다. 자주 사용되는 일반적인 기능은 모듈로 한번 만들어 두면, 필요한 프로그램마다 도입하여 활용할 수도 있다.

파이썬 문화에서는 서로 연관된 데이터, 클래스, 함수 등의 정의를 모아 둔 파이썬 소스 코드를 모듈이라고 한다. 예를 들어, 어떤 게임 프로그램에 다음 세 기능이 구현되어 있다고 하자.

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

이 기능들은 전체 게임을 구성하는 필수적인 요소이지만, 각각 담당하는 내용이 구별되는 것이기도 하다. 이들을 모두 하나의 파이썬 파일에 작성해 둔다면 파일 하나에 코드가 너무 많아져 관리하기 불편하지 않을까? 수많은 데이터·클래스·함수 정의를 기능별로 분류해 각각 별도의 파일로 나누어 둔다면 프로그램으 더 편리하게 관리할 수 있을 것이다. 이렇게 나누어 둔 각각의 소스 코드 파일이 바로 모듈이다.

그림 10-1 게임을 구성하는 모듈 (준비중)

프로그램의 여러 기능을 모듈로 나누어 두면, 기능을 다른 프로그램에서 재사용할 수 있다는 장점도 있다. 예를 들어 그림 그리는 프로그램을 만들 때 ‘화면 출력’ 기능 모듈을 사용할 수 있을 것이다.

그림 10-2 모듈의 재사용 (준비중)

라이브러리

모듈을 활용하면 기능을 다시 구현하는 낭비를 줄이고, 프로그램의 평균 품질도 향상시킬 수 있다. 인터넷에 접속하는 기능, 시간을 측정·표현하는 기능 등 여러 프로그램에 공통적으로 필요한 기능이 많다. 이를 모든 프로그래머들이 제각각 만든다면 엄청난 양의 노력이 낭비될 것이다. 게다가 프로그래머의 실력은 제각각이므로, 그 기능의 품질도 제각각이 될 것이다. 반면, 우수한 프로그래머가 이런 기능을 모듈로 만들어 공유한다면 시간을 절약할 뿐 아니라 모두가 우수한 기능을 프로그램에 포함시킬 수 있다.

다른 프로그램에 포함시키기 위해 기능을 미리 만들어 묶어 둔 것을 라이브러리(library)라고 한다. 대부분의 프로그램에 필요한 기본 기능은 대개 프로그래밍 언어와 함께 제공되는데, 이를 표준 라이브러리(standard library)라고 한다. 한편, 비공식적이지만 다른 누군가가 만든 모듈도 프로그래밍에 활용할 때가 있다. 이를 제 3자 라이브러리(third-party library)라고 한다.

파이썬에서도 다양한 라이브러리를 활용할 수 있다. 파이썬의 라이브러리는 여러 모듈을 모아둔 것이며, 누가 제공하는지에 따라 다음과 같이 분류할 수 있다.

  • 표준 라이브러리: 파이썬 인터프리터와 함께 제공되는 기본 모듈의 모음. 표준 라이브러리는 언어의 ‘단어장’ 또는 ‘실용 회화 사전’에 비견할 수 있다. 두루두루 알아두면 프로그램에 필요한 다양한 기능을 빠르게 구현할 수 있다. 표준 라이브러리는 방대하여 이 책에서 다 다룰 수는 없지만, 자주 사용되는 것을 11장에서 설명한다.
  • 제 3자 라이브러리: 오픈 소스 커뮤니티, 회사, 개인 등 다양한 사람들이 제작·배포하는 비표준 모듈의 모음. 이 책의 부록에서 제 3자 라이브러리를 구하는 방법과 설치하는 방법을 간단히 안내한다. 제 3자 라이브러리를 사용할 때는 이용약관·사용료 등 법률적 문제에 유의해야 한다.
  • 개인 모듈: 여러분이 프로젝트에 활용하기 위해 직접 제작한 비공개 모듈. 모듈 만드는 법을 이 장에서 알아본다.

표준 모듈은 그동안 몇 차례 사용해 본 적이 있다. 예를 들어, 제곱근을 구하는 함수 math.sqrt()를 사용기 위해 import math 명령을 실행해야 한다는 것을 기억할 것이다. 이 때의 import 문은 모듈을 프로그램 안으로 가져와 사용하기 위한 명령이고, math 모듈은 수학 계산과 관련된 기능을 모아 놓은 표준 모듈이다.

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 모듈이 아니라 모듈 속의 특정 객체만이 변수에 대입된다. 따라서 math 모듈은 가리킬 수 없다.

10.1.3 모듈 만들기

모듈을 임포트하는 방법은 알겠는데, 만들 때는 어떻게 할까? 사실 여러분은 예제를 따라 입력하거나 연습문제를 풀면서 모듈을 여러 번 만들어 봤다. 파이썬 코드를 모아둔 소스 코드 파일이 바로 파이썬의 모듈이기 때문이다.

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

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

코드 10-5 학생 정보를 계산하는 프로그램

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
칭찬을 가장 많이 받은 학생: 김파이

그런데 이 프로그램을 만든 선생님이 생각해보니 산술평균, 중앙값, 최빈값을 구하는 함수는 다른 프로그램에서도 자주 사용할 수 있을 것 같다. 따라서 모듈로 분리해두려 한다. 이를 위해서는 함수를 소스 코드에서 잘라내고, 파이참에서 새 파일을 만들어 붙여넣으면 된다. 파일 이름이 곧 모듈명이 되므로, 새로 만들 파일의 이름을 잘 지어야 한다. 모듈의 내용이 시퀀스를 계산하는 함수들이니 sequence.py 정도로 파일 이름을 짓자. (통계를 뜻하는 statistics.py도 적절하지만, 파이썬의 기본 통계 모듈 이름과 겹치므로 사용하지 않는 것이 좋다.)

그림 10-3 프로그램에서 모듈로 분리할 코드를 잘라내기

그림 10-3 프로그램에서 모듈로 분리할 코드를 잘라내기

그림 10-4 잘라낸 코드를 새 파일에 붙여넣고 sequence.py 이름으로 저장하기

그림 10-4 잘라낸 코드를 새 파일에 붙여넣고 sequence.py 이름으로 저장하기

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

코드 10-6 분리한 모듈 (sequence.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 sequence 명령으로 새로 정의한 sequence 모듈을 임포트해야 한다. 또한, sequence.mean()과 같이 각 함수를 가리킬 때 모듈명을 명시해주어야 한다.

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

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

import sequence    # sequence 모듈(sequence.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('기말고사 성적 평균:', sequence.mean(scores))
print('가운데 앉은 학생의 키:', sequence.median(heights))
print('칭찬을 가장 많이 받은 학생:', sequence.most_frequent(compliments))

실행 결과:

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

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

10.1.4 최상위 모듈

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

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

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

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

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

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