zipapp — 실행 가능한 Python zip 아카이브 관리
zipapp — 실행 가능한 Python zip 아카이브 관리
zipapp 모듈은 Python 인터프리터가 직접 실행할 수 있는, Python 코드를 담은 zip 파일을 만드는 도구를 제공해요. 명령줄 인터페이스(CLI)와 Python API 둘 다 제공합니다.
출처: Python 표준 라이브러리
버전 3.5에서 추가.
본문
zipapp 모듈은 Python 코드를 담은 zip 파일 생성·관리 도구를 제공합니다. 그 아카이브는 Python 인터프리터가 직접 실행할 수 있어요. 모듈은 명령줄 인터페이스와 Python API를 모두 제공합니다.
기본 예제 (Basic Example)
다음 예는 명령줄 인터페이스로 Python 코드가 담긴 디렉터리에서 실행 가능한 아카이브를 만드는 방법을 보여 줘요. 실행하면 아카이브 안의 myapp 모듈에서 main 함수를 실행합니다.
$ python -m zipapp myapp -m "myapp:main"
$ python myapp.pyz
<output from myapp>
명령줄 인터페이스 (Command-Line Interface)
명령줄에서 프로그램으로 호출하면 다음 형태를 사용합니다.
$ python -m zipapp source [options]
source가 디렉터리면 그 내용으로 아카이브를 만듭니다. source가 파일이면 아카이브여야 하며, 대상 아카이브로 복사합니다 (또는 --info 옵션을 지정하면 shebang 줄의 내용을 표시해요).
다음 옵션을 이해합니다.
-o <output>,--output=<output>— 출력을output이라는 이름의 파일에 씁니다. 이 옵션을 지정하지 않으면 출력 파일 이름은 입력source와 같고 확장자.pyz가 붙어요. 명시적 파일 이름을 주면 그대로 쓰입니다 (필요하면.pyz확장자를 포함해야 해요).source가 아카이브일 때는 출력 파일 이름을 반드시 지정해야 합니다 (그 경우output이source와 같으면 안 돼요).-p <interpreter>,--python=<interpreter>— 아카이브에#!줄을 추가해interpreter를 실행할 명령으로 지정합니다. 또한 POSIX에서는 아카이브를 실행 가능하게 만듭니다. 기본은#!줄을 쓰지 않고 파일도 실행 가능하게 만들지 않아요.-m <mainfn>,--main=<mainfn>—mainfn을 실행하는__main__.py파일을 아카이브에 씁니다.mainfn인자는"pkg.mod:fn"형태여야 하는데, 여기서"pkg.mod"는 아카이브 안의 패키지/모듈이고"fn"은 그 모듈 안의 호출 가능한 객체(callable)예요.__main__.py파일이 그 callable을 실행합니다. 아카이브를 복사할 때는--main을 지정할 수 없습니다.-c,--compress— deflate 방식으로 파일을 압축해 출력 파일 크기를 줄입니다. 기본적으로 파일은 아카이브에 압축하지 않고 저장돼요. 아카이브를 복사할 때는--compress효과가 없습니다. (버전 3.7에서 추가)--info— 진단 목적으로 아카이브에 내장된 인터프리터를 표시합니다. 이 경우 다른 옵션은 무시되고SOURCE는 디렉터리가 아닌 아카이브여야 해요.-h,--help— 짧은 사용법 메시지를 출력하고 종료합니다.
Python API
모듈은 두 개의 편의 함수를 정의합니다.
zipapp.create_archive(source, target=None, interpreter=None, main=None, filter=None, compressed=False)
source에서 애플리케이션 아카이브를 만듭니다. source는 다음 중 하나일 수 있어요.
- 디렉터리 이름 또는 디렉터리를 가리키는 path류 객체 — 그 디렉터리 내용으로 새 애플리케이션 아카이브를 만듭니다.
- 기존 애플리케이션 아카이브 파일의 이름 또는 그런 파일을 가리키는 path류 객체 — 파일을
target으로 복사합니다 (interpreter인자에 준 값을 반영하도록 수정하면서). 필요하면 파일 이름에.pyz확장자를 포함해야 해요. - 바이트 모드로 읽기 위해 열린 파일 객체 — 파일 내용이 애플리케이션 아카이브여야 하며, 파일 객체가 아카이브 시작에 위치해 있다고 가정합니다.
target 인자는 결과 아카이브가 쓰일 곳을 결정합니다.
- 파일 이름 또는 path류 객체라면 아카이브를 그 파일에 씁니다.
- 열린 파일 객체라면 아카이브를 그 파일 객체에 씁니다. 바이트 모드로 쓰기 위해 열려 있어야 해요.
target을 생략하면(또는None)source는 디렉터리여야 하고,target은source와 같은 이름에.pyz확장자가 붙은 파일이 됩니다.
interpreter 인자는 아카이브를 실행할 Python 인터프리터의 이름을 지정합니다. 아카이브 시작 부분에 "shebang" 줄로 쓰여요. POSIX에서는 OS가 해석하고, Windows에서는 Python 런처가 처리합니다. interpreter를 생략하면 shebang 줄이 쓰이지 않아요. interpreter를 지정하고 target이 파일 이름이면 대상 파일의 실행 비트가 설정됩니다.
main 인자는 아카이브의 main 프로그램으로 쓰일 callable의 이름을 지정합니다. source가 디렉터리이고 그 디렉터리가 이미 __main__.py를 담고 있지 않을 때만 지정할 수 있어요. main 인자는 "pkg.module:callable" 형태여야 하며, 아카이브는 "pkg.module"을 import해서 주어진 callable을 인자 없이 실행하는 방식으로 실행됩니다. source가 디렉터리이고 __main__.py 파일이 없는데 main을 생략하면 오류입니다. 그렇지 않으면 결과 아카이브가 실행 불가능해지기 때문이에요.
선택 filter 인자는 추가 중인 파일의 경로(source 디렉터리 기준)를 나타내는 Path 객체를 넘겨받는 콜백 함수를 지정합니다. 파일을 추가할 것이면 True를 반환해야 해요.
선택 compressed 인자는 파일을 압축할지 결정합니다. True로 설정하면 아카이브 안의 파일을 deflate 방식으로 압축하고, 아니면 압축하지 않고 저장해요. 기존 아카이브를 복사할 때는 이 인자 효과가 없습니다.
source나 target에 파일 객체를 지정했다면, create_archive 호출 후 닫는 것은 호출자 책임입니다.
기존 아카이브를 복사할 때 제공된 파일 객체는 read·readline 또는 write 메서드만 있으면 돼요. 디렉터리에서 아카이브를 만들 때 target이 파일 객체라면 zipfile.ZipFile 클래스에 전달되므로 그 클래스가 필요로 하는 메서드를 제공해야 합니다.
버전 3.7에서 변경:
filter와compressed매개변수 추가.
zipapp.get_interpreter(archive)
아카이브 시작 부분의 #! 줄에 지정된 인터프리터를 반환합니다. #! 줄이 없으면 None을 반환해요. archive 인자는 파일 이름이거나 바이트 모드로 읽기 위해 열린 파일류 객체일 수 있습니다. 아카이브 시작에 있다고 가정해요.
예제 (Examples)
디렉터리를 아카이브로 묶고 실행합니다.
$ python -m zipapp myapp
$ python myapp.pyz
<output from myapp>
create_archive() 함수로도 같은 일을 할 수 있어요.
>>> import zipapp
>>> zipapp.create_archive('myapp', 'myapp.pyz')
POSIX에서 애플리케이션을 직접 실행 가능하게 하려면 사용할 인터프리터를 지정합니다.
$ python -m zipapp myapp -p "/usr/bin/env python"
$ ./myapp.pyz
<output from myapp>
기존 아카이브의 shebang 줄을 바꾸려면 create_archive() 함수로 수정된 아카이브를 만듭니다.
>>> import zipapp
>>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')
파일을 제자리에서 갱신하려면 BytesIO 객체를 사용해 메모리에서 교체한 뒤 source를 덮어씁니다. 제자리에서 파일을 덮어쓸 때 오류가 나면 원본 파일을 잃을 위험이 있다는 점을 알아 두세요. 이 코드는 그런 오류로부터 보호하지 않지만, 프로덕션 코드는 보호해야 합니다. 또한 이 방법은 아카이브가 메모리에 들어갈 때만 동작해요.
>>> import zipapp
>>> import io
>>> temp = io.BytesIO()
>>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
>>> with open('myapp.pyz', 'wb') as f:
>>> f.write(temp.getvalue())
인터프리터 지정 (Specifying the Interpreter)
인터프리터를 지정한 다음 애플리케이션 아카이브를 배포한다면, 사용하는 인터프리터가 이식 가능한지 확인해야 해요. Windows용 Python 런처는 대부분의 일반적인 POSIX #! 줄을 지원하지만 고려할 다른 문제들이 있습니다.
"/usr/bin/env python"(또는"/usr/bin/python"같은 "python" 명령의 다른 형태)을 쓰면, 사용자의 기본이 Python 2인지 3인지 모르므로 두 버전 모두에서 동작하도록 코드를 작성해야 해요."/usr/bin/env python3"처럼 명시적 버전을 쓰면 그 버전이 없는 사용자에게는 애플리케이션이 동작하지 않습니다 (코드를 Python 2와 호환되게 만들지 않았다면 그게 원하는 것일 수도 있어요).- "python X.Y 이상"이라고 말할 방법이 없으므로,
"/usr/bin/env python3.4"같은 정확한 버전을 쓰면 조심해야 해요. 예를 들어 Python 3.5 사용자를 위해 shebang 줄을 바꿔야 할 테니까요. - 보통 코드가 Python 2용인지 3용인지에 따라
"/usr/bin/env python2"나"/usr/bin/env python3"을 쓰는 게 일반적입니다.
zipapp으로 독립 실행형 애플리케이션 만들기 (Creating Standalone Applications)
zipapp 모듈을 쓰면 자체 포함(self-contained) Python 프로그램을 만들 수 있어요. 시스템에 적절한 버전의 Python이 설치된 최종 사용자에게 배포할 수 있죠. 핵심은 애플리케이션의 모든 의존성을 애플리케이션 코드와 함께 아카이브에 묶는 것입니다.
독립 실행형 아카이브를 만드는 단계는 다음과 같아요.
- 평소처럼 디렉터리에 애플리케이션을 만듭니다.
myapp디렉터리에__main__.py파일과 지원 코드가 있게 하세요. pip로 애플리케이션의 모든 의존성을myapp디렉터리에 설치합니다.
(프로젝트 요구사항이$ python -m pip install -r requirements.txt --target myapprequirements.txt에 있다고 가정합니다. 없으면 pip 명령줄에 의존성을 직접 나열하면 돼요.)- 애플리케이션을 묶습니다.
$ python -m zipapp -p "interpreter" myapp
이렇게 하면 적절한 인터프리터가 있는 어떤 머신에서든 실행할 수 있는 독립 실행형 실행 파일이 만들어져요. 인터프리터 지정은 인터프리터 지정을 참고하세요. 단일 파일로 사용자에게 배포할 수 있습니다.
Unix에서는 myapp.pyz 파일이 그 자체로 실행 가능해요. "평범한" 명령 이름을 원하면 파일 이름을 바꿔 .pyz 확장자를 제거할 수 있습니다. Windows에서는 Python 인터프리터가 설치될 때 .pyz와 .pyzw 파일 확장자를 등록하므로 myapp.pyz[w] 파일이 실행 가능합니다.
주의사항 (Caveats)
애플리케이션이 C 확장을 포함한 패키지에 의존한다면, 그 패키지는 zip 파일에서 실행할 수 없어요 (실행 가능한 코드가 OS 로더가 로드하도록 파일시스템에 존재해야 한다는 OS 제한 때문입니다). 이 경우 해당 의존성을 zip 파일에서 제외하고, 사용자에게 설치를 요구하거나 zip 파일 옆에 함께 제공해 압축 해제된 모듈이 담긴 디렉터리를 sys.path에 포함하는 코드를 __main__.py에 추가하면 됩니다. 이 경우 대상 아키텍처에 맞는 적절한 바이너리를 제공해야 해요 (그리고 런타임에 사용자의 머신에 따라 sys.path에 추가할 올바른 버전을 골라야 할 수도 있습니다).
Python Zip 애플리케이션 아카이브 형식 (The Python Zip Application Archive Format)
Python은 2.6 버전부터 __main__.py 파일을 담은 zip 파일을 실행할 수 있었어요. Python이 실행하려면 애플리케이션 아카이브는 단순히 애플리케이션의 진입점으로 실행될 __main__.py 파일을 담은 표준 zip 파일이기만 하면 됩니다. 평소의 Python 스크립트와 마찬가지로 스크립트(여기선 zip 파일)의 부모가 sys.path에 놓이므로 zip 파일에서 추가 모듈을 import할 수 있어요.
zip 파일 형식은 zip 파일 앞에 임의의 데이터를 붙일 수 있게 허용합니다. zip 애플리케이션 형식은 이 능력으로 파일 앞에 표준 POSIX "shebang" 줄(#!/path/to/interpreter)을 붙입니다.
따라서 공식적으로 Python zip 애플리케이션 형식은 다음과 같아요.
- 선택적 shebang 줄:
b'#!'문자, 인터프리터 이름, 그다음 개행(b'\n') 문자를 포함합니다. 인터프리터 이름은 OS "shebang" 처리나 Windows의 Python 런처가 받아들일 수 있는 무엇이든 돼요. 인터프리터는 Windows에서는 UTF-8로, POSIX에서는sys.getfilesystemencoding()으로 인코딩해야 합니다. zipfile모듈이 생성하는 표준 zipfile 데이터. zipfile 내용은__main__.py라는 파일을 포함해야 하며, 이 파일은 zipfile의 "루트"에 있어야 해요 (즉 하위 디렉터리에 있으면 안 됩니다). zipfile 데이터는 압축되거나 압축되지 않을 수 있어요.
애플리케이션 아카이브에 shebang 줄이 있으면 POSIX 시스템에서 실행 비트가 설정되어 직접 실행될 수 있습니다.
이 모듈의 도구를 써서 애플리케이션 아카이브를 만들어야 한다는 요구는 없어요. 이 모듈은 편의일 뿐이며, 어떤 수단으로든 위 형식으로 만든 아카이브는 Python이 받아들입니다.