Watch at Ease세상을 읽고, 일상의 답을 찾다.

기술 · 실용 가이드

Windows의 Python NamedTemporaryFile: 다시 열고 확실히 지우기

첫 PermissionError와 나중 unlink 실패는 원인이 다릅니다. 삭제 경계를 고른 뒤 모든 보조 핸들을 그 전에 닫으세요.

Windows에서 세 가지 NamedTemporaryFile 삭제 방식을 비교한 자체 제작 표.
자체 제작 기술 일러스트: 자체 제작 기술 일러스트: 다른 프로그램에 경로를 넘기기 전에 삭제 경계를 고릅니다. Watch at Ease

Windows에서 NamedTemporaryFile은 두 시점에 실패할 수 있습니다. 기본값에서는 다른 프로그램이 경로를 열지 못하고, 플래그를 바꾼 뒤에는 읽기는 성공해도 핸들이 남아 컨텍스트 종료 시 정리가 실패할 수 있습니다.

임시 경로를 소유권 인계로 다루세요. 삭제를 파일 닫기, 컨텍스트 종료, finally 중 어디서 할지 먼저 정하고 모든 소비자를 그 경계 전에 끝냅니다.

1. 어떤 PermissionError인지 구분하기

서브프로세스, 브라우저 또는 업로드 라이브러리가 경로를 열지 못하면 생성자가 쥔 Windows 핸들과 두 번째 열기의 공유 조건이 맞지 않는 것입니다. CPython은 Windows에서 기본 delete=True와 delete_on_close=True에 O_TEMPORARY를 쓰지만, 내장 open과 많은 외부 프로그램은 같은 삭제 공유를 요청하지 않습니다.

소비자가 파일을 읽었지만 컨텍스트 종료나 os.unlink에서 실패하면 아직 열린 다른 핸들을 찾으세요. Microsoft 문서에 따르면 공유 모드는 핸들이 닫힐 때까지 유지되고 삭제는 호환 핸들을 기다립니다. 플래그 하나를 바꿔도 업로드 스트림이나 서브프로세스가 자동으로 닫히지는 않습니다.

2. Python 3.12+에서는 컨텍스트 종료 정리를 우선하기

일반 외부 프로그램에는 with NamedTemporaryFile(delete=True, delete_on_close=False) as tmp:를 사용합니다. tmp가 열린 채 읽는다면 flush하고, 독점 접근이 필요하면 블록 안에서 close합니다. 경로는 with가 끝날 때까지 남고 그때 Python이 os.unlink를 시도합니다.

블록을 나가기 전에 서브프로세스를 기다리고 업로드를 위해 별도로 연 파일 객체를 모두 닫으세요. 삭제 공유를 하지 않는 보통의 두 번째 열기는 먼저 닫아야 합니다. 디렉터리에 삭제 권한이 없으면 PermissionError가 남으므로 실제 배포 위치에서 검사하세요.

3. 이전 Python에서는 finally로 정리하기

Python 3.12 이전이거나 경로를 넘기기 전에 래퍼를 닫아야 한다면 delete=False를 씁니다. NamedTemporaryFile을 닫고 소비자를 호출한 뒤 finally에서 경로를 삭제합니다. delete=False에서는 delete_on_close가 무시되므로 정리는 전적으로 애플리케이션 책임입니다.

생성 성공 뒤에만 경로를 저장하고 finally에서 보조 스트림을 닫은 다음 os.unlink를 호출하세요. tempfile.mktemp는 파일을 안전하게 만들지 않아 경쟁 조건을 추가하므로 회피책으로 쓰면 안 됩니다.

4. 전체 핸들 수명 주기 감사하기

인계 전에 쓰기를 flush하거나 필요하면 생성 핸들을 닫고 경로가 있는지 확인하세요. 소비 중에는 비동기로 띄운 뒤 즉시 컨텍스트를 빠져나오지 말고 완료를 기다립니다. 파일 객체를 받는 라이브러리에는 open(tmp.name, 'rb') 자체를 with로 감싸 업로드 핸들을 확실히 닫습니다.

정리 전에 파일 객체, 서브프로세스, 메모리 매핑을 점검하세요. 정리 후에는 경로가 사라졌는지 확인하고 실패를 정확히 노출합니다. 재시도는 열린 핸들을 숨길 수 있으며 누가 파일을 쥐었는지 밝히는 대신이 아닙니다.

5. 이식성 경계를 명시하기

POSIX는 열린 명명 임시 파일을 보통 다시 열 수 있지만 Windows 수명 주기가 안전하다는 뜻은 아닙니다. 모든 플랫폼에서 같은 명시적 인계와 정리 순서를 사용해 macOS/Linux 테스트만 통과하는 문제를 막으세요.

기본 close 시 삭제는 모든 열기가 삭제를 공유할 때 가장 강하고, 컨텍스트 종료 정리는 일반 프로그램에 편하지만 중단될 수 있습니다. delete=False는 호환성이 넓지만 finally와 시작 시 잔여 파일 정리가 필요합니다. 소비자 실패와 정리 실패를 모두 테스트하세요.

생성, 외부 사용, 정리를 잇는 자체 제작 흐름도.
자체 제작 기술 일러스트: 자체 제작 기술 일러스트: 호환되지 않는 보조 핸들은 정리 전에 모두 닫습니다. Watch at Ease