14  모듈(modules)과 패키지(packages)

14.1 모듈과 import 문

모든 파이썬 소스 파일은 모듈이 될 수 있다. 이를테면 다음과 같은 소스 코드를 가진 module.py라는 파일이 있다고 생각해 보자. 이 파일은 글로벌 변수(아래 내용 참고) a, 함수 func, 클래스 SomeClass, 그리고 print 실행문을 가지고 있다.

# module.py
a = 37
def func():
    print(f'func says that a is {a}')
class SomeClass:
    def method(self):
        print('method says hi')
print('loaded module')

이 모듈을 임포트할 때는 import module을 사용한다.

import module
loaded module

모듈을 로딩하면 이 이름을 가지고 모듈의 값이나 함수, 클래스 등을 사용할 수 있게 된다.

module.a
37
module.func()
s = module.SomeClass()
s.method()
func says that a is 37
method says hi
파이썬에서 global의 의미
  • In Python, global variables are not global to all modules, but rather are attributes of a single module object.

14.1.1 import module 실행되는 과정

import module를 실행시키면 여러 가지 과정들이 진행된다.

  1. module.py 소스 파일을 찾는다. 파이썬 인터프리터가 모듈을 찾는 것은 sys.path에 정의된 디렉터리 순서에 따른다. 찾지 못하면 ImportError 예외가 발생한다.
  2. 새로운 모듈 객체(module object)가 생성된다. 이 객체는 모듈에 포함된 모든 글로벌 정의들을 담는 컨테이터 역할을 한다. 이것을 namespace라고 부른다.
  3. 모듈 소스 코드가 새롭게 생성된 module namespace에서 실행된다.
  4. 에러가 발생하지 않으면 새롭게 만든 모듈 객체에 대한 이름이 부른 장소에 생성된다. module.py인 경우 그 이름은 module이 된다.

import 문은 모듈을 로딩하면서 안의 소스 코드를 모두 실행한다. 그래서 print('loaded module') 문이 실행된다. 그리고 모듈에서 정의되는 함수, 클래스 등은 모듈 namespace에 저장한다.

여러 개의 모듈들을 한꺼번에 로딩할 때는 모듈 이름을 코마로 구분한다.

import os, re

모듈의 이름을 바꾸어 사용하고자 하는 경우에는 끝에 as 구에 사용할 이름을 추가한다.

import module as mo 
Binding

이름을 값에 연결하는 과정을 binding이라고 한다. =을 사용한 할당, def를 이용한 함수 정의, class를 이용한 클래스 정의와 마찬가지로 import 문도 역시 모듈 객체를 어떤 이름에 대응시키는 binding 과정의 한 종류이다.

14.2 모듈 캐싱: sys.modules 딕셔너리

파이썬 모듈의 소스 코드는 로딩되어 딱 한번만 로딩된다.

따라서, 모듈을 import 하고 나서, 모듈의 소스 코드가 변경된 다음, 다시 import를 실행해도 바뀐 모듈이 로딩되지 않는다.

첫 번째 import 문이 실행할 때 만든 모듈 객체를 sys.modules라는 딕셔너리에 캐싱한다. 이 딕셔너리의 키는 모듈 이름이고 대응하는 값은 모듈 객체이다. 참고로 현재 세션에서 로딩된 모듈의 개수을 보자.

import sys
len(sys.modules.keys())
1769

import 문을 사용하여 원래의 모듈을 다시 로딩하려고 시도해도, 이 경우는 원래의 소스 코드를 가지고 로딩 작업을 하지 않고, 캐싱되어 있는 sys.modules에서 모듈 객체를 가지고 온다.

14.3 from

from 문은 모듈에서 특정 이름(정의)만 로딩하는 데 사용된다.

from module import func

from module import name은 모듈 cache에 저장된 이름을 현재의 namespace로 가지고 온다. 즉, 먼저 import module을 실행하고 나서, cache에 저장된 것을 가지고 온다.

14.4 모듈 컴파일

어떤 모듈이 처음으로 파이썬으로 임포트될 때, 모듈의 소스 코드가 파이썬 인터프리터에 의해서 bytecode로 컴파일되고, 이 컴파일된 코드는 __pycache__라는 디렉터리 안에 .pyc라는 파일로 저장된다. 보통 .py 파일과 같은 위치에 저장된다.

이런 상태에서 다른 파이썬 프로그램에서 이 모듈을 임포트할 경우, 이 컴파일된 bytecode가 로딩된다. 따라서 import 과정이 빨라진다.

따라서, 가상 환경을 만들고 numpy 등의 모듈을 설치한 후 처음 사용할 때 컴파일되기 때문에 시간이 좀 걸린다. 하지만 다음에 또 사용하는 경우에는 bytecode가 바로 로딩되기 때문에 시간이 빨라진다.

14.5 sys.path

우리가 어떤 파이썬 소스를 모듈로 사용하려 한다고 해 보자. 이 소스 파일을 어디에 두어야 파이썬 인터프리터가 인식할까?

sys.path는 파이썬 리스트인데, 여기에는 현재의 파이썬 인터프리터가 모듈을 찾을 때 사용하는 디렉터리들이 순서대로 들어있다. 보통의 Python REPL에서는 첫 번째는 빈문자열인데, 이것은 현재의 디렉터리를 의미한다.

모듈이 들어 있는 경로를 sys.path 리스트에 append() 메서드 등으로 추가하면 해당 모듈을 불러올 수 있다.

import sys
sys.path
['/Users/seokbumko/Learning/Education/cds-python',
 '/opt/anaconda3/lib/python312.zip',
 '/opt/anaconda3/lib/python3.12',
 '/opt/anaconda3/lib/python3.12/lib-dynload',
 '',
 '/opt/anaconda3/lib/python3.12/site-packages',
 '/opt/anaconda3/lib/python3.12/site-packages/aeosa',
 '/opt/anaconda3/lib/python3.12/site-packages/setuptools/_vendor']

14.6 if __name__ == "__main__":

모든 모듈 객체는 __name__ attribute에 모듈의 이름(문자열)을 저장한다. 파이썬 최상위 모듈의 __name__의 값은 "__main__"이다. 이 정보를 활용하여, 어떤 모듈이 (1) 다른 모듈로 임포트될 때와 (2) 하나의 독립된 스크립트로 실행될 때 다르게 실행되게 만들 수 있다. 보통 다음과 같이 코딩한다.

if __name__ == '__main__':
     # Yes. 메인 스크립트로 실행
     statements
else:
     # No, 모듈로 임포트될 때 실행 
     statements

14.7 패키지(package): 모듈의 모음

패키지는 나무 같은 위계 구조로 되어 있는 모듈의 집합이다. 패키지는 __init__.py라고 하는 파일을 가진 디렉터리에 만든다. 여기에 파이썬 소스 파일이나 서브패키지를 배치한다.

다음은 graphics라는 패키지를 만든 예이다.

graphics/
      __init__.py
      primitive/
         __init__.py
         lines.py
         fill.py
         text.py
         ...
      graph2d/
         __init__.py
         plot2d.py
         ...
      graph3d/
         __init__.py
         plot3d.py
         ...
      formats/
         __init__.py
         gif.py
         png.py
         tiff.py
         jpeg.py

import 문에서는 .을 사용하여 이 위계를 표현한다. 다음은 그 예이다.

import graphics.primitive.fill
from graphics.primitive import fill