atexit — 종료 핸들러

atexit — 종료 핸들러 (Exit handlers)

atexit 모듈은 exit handler를 등록·등록 해제하는 함수를 정의해요. exit handler는 "프로그램 종료 시" 자동으로 실행되는 함수예요. 예를 들어 sys.exit()가 호출되거나 메인 모듈의 실행이 완료될 때, 더 일반적으로는 인터프리터 셧다운 때 실행되죠.

출처: Python 표준 라이브러리

본문

atexit 모듈은 exit handler를 등록·등록 해제하는 함수를 정의해요. exit handler는 정상적인 프로그램 종료(예: sys.exit() 호출이나 메인 모듈 실행 완료) 시, 더 일반적으로는 인터프리터 셧다운 시 자동으로 실행되는 함수예요.

종료 시 모든 등록된 exit handler가 등록된 역순으로 호출돼요. A, B, C를 등록하면 인터프리터 셧다운 때 C, B, A 순서로 실행돼요. 그 가정은 저수준 모듈이 보통 고수준 모듈보다 먼저 import되고 따라서 더 나중에 정리돼야 한다는 거예요.

exit handler 실행 중 예외가 발생하면 traceback이 출력되고(SystemExit 발생 시 제외) 예외 정보가 저장돼요. 모든 exit handler가 실행될 기회를 가진 뒤, 마지막으로 발생한 예외가 다시 발생해요.

여러 인터프리터를 사용하는 프로그램에서는 각 인터프리터가 자체 exit handler 스택을 갖고, 인터프리터가 셧다운될 때(예: concurrent.interpreters.Interpreter.close() 또는 C API Py_EndInterpreter()) 실행돼요. 이 모듈의 등록 함수는 호출된 인터프리터에만 영향을 줘요.

참고: exit handler는 Python이 처리하지 않는 신호로 프로그램이 죽었을 때, Python 치명적 내부 오류가 감지됐을 때, 또는 os._exit()가 호출됐을 때는 호출되지 않아요.

참고: 정리(cleanup) 함수 안에서 함수를 등록하거나 등록 해제하는 효과는 정의되지 않았어요.

경고: exit handler를 작성할 때, 특히 C API 확장에서, 다른 exit handler가 당신이 정리한 뒤에도 여전히 임의의 Python 코드를 실행할 수 있다는 점을 명심하세요. 그런 코드는 크래시가 아니라 예외로 성공하거나 실패해야 해요.

3.12 버전 변경: exit handler에서 새 스레드를 시작하거나 os.fork()로 새 프로세스를 만드는 시도는 이제 RuntimeError로 이어져요. 이전에는 메인 Python 런타임 스레드가 스레드 상태를 해제하는 동안 내부 스레딩 루틴이나 새 프로세스가 그 상태를 사용하려고 하면서 경쟁 조건이 생길 수 있었고, 깨끗한 셧다운이 아니라 크래시로 이어질 수 있었어요.

3.7 버전 변경: 서브인터프리터와 함께 사용하면 등록된 함수는 등록된 인터프리터에 국한돼요.

atexit.register(func, *args, **kwargs)

func를 exit handler로 등록해요. func에 전달할 선택 인자는 register()의 인자로 전달해야 해요. 같은 함수와 인자를 두 번 이상 등록하는 것도 가능해요. 이 함수는 func를 반환하므로 데코레이터로 쓸 수 있어요.

atexit.unregister(func)

exit handler 목록에서 func를 제거해요. func가 이전에 등록되지 않았다면 unregister()는 조용히 아무것도 하지 않아요. func가 두 번 이상 등록됐다면 atexit 호출 스택에서 그 함수의 모든 출현이 제거돼요. 등록 해제 동안 내부적으로 동등성 비교(==)가 사용되므로 함수 참조가 일치하는 정체성을 가질 필요는 없어요.

더 알아보기 — Module readline: atexit로 readline 히스토리 파일을 읽고 쓰는 유용한 예시.

atexit 예제

다음 간단한 예시는 모듈이 import될 때 파일에서 카운터를 초기화하고, 애플리케이션이 종료 시 이 모듈에 명시적으로 호출하는 데 의존하지 않고 프로그램이 종료될 때 카운터의 갱신된 값을 자동으로 저장하는 방법을 보여줘요.

try:
    with open('counterfile') as infile:
        _count = int(infile.read())
except FileNotFoundError:
    _count = 0

def incrcounter(n):
    global _count
    _count = _count + n

def savecounter():
    with open('counterfile', 'w') as outfile:
        outfile.write('%d' % _count)

import atexit

atexit.register(savecounter)

위치 인자와 키워드 인자도 register()에 전달해 등록된 함수가 호출될 때 함께 전달되게 할 수 있어요.

def goodbye(name, adjective):
    print('Goodbye %s, it was %s to meet you.' % (name, adjective))

import atexit

atexit.register(goodbye, 'Donny', 'nice')
# or:
atexit.register(goodbye, adjective='nice', name='Donny')

데코레이터로 사용:

import atexit

@atexit.register
def goodbye():
    print('You are now leaving the Python sector.')

이것은 인자 없이 호출될 수 있는 함수에만 동작해요.