본문 바로가기
Python

py2exe - python 스크립트를 exe파일로 만들자

by irmus 2008. 1. 9.
py2exe는 python으로 만들어진 .py 스크립트를 Windows에서 사용가능한 .exe 파일로 변환해 주는 python 모듈이다.




시작하기
  1. py2exe를 다운로드받아 설치한다. http://sourceforge.net/project/showfiles.php?group_id=15583
  2. py2exe는 그 자체로 python script이다. 실행하려면 python이 설치되어 있어야만 한다.
  3. 자신이 사용하는 python 버젼에 맞는 바이너리를 다운로드 받도록 하자.
  4. 다운로드받은 파일을 실행시키면 별다른 설정 없이 간단하게 설치할 수 있다.




간단 예제

간단한 예제 스크립트를 만들어 .exe로 변환해 보는 step-by-step 실습.

print 'Hello World!!'
위와 같은 간단한 스크립트 파일을 만들어 저장한다.
from distutils.core import setup
import py2exe

setup(console=['simple.py'])
다음 단계로 위와같은 setup 스크립트를 만든다.
첫번째 단계에서 만든 .exe로 변환하고자 하는 스크립트 파일의 이름이 사용된 것을 확인할 수 있다.


마지막 단계는 setup.py를 이용해서 simple.py파일을 simple.exe로 변환하는 단계이다.

D:\MyDocuments\python\work\TestPy2Exe>python setup.py py2exe

simple.py와 setup.py가 저장된 디랙토리에서 위와 같이 실행하면 변환이 이루어 지며 아래와 같은 메시지가 출력된다.

D:\MyDocuments\python\work\TestPy2Exe>python setup.py py2exerunning py2exe
creating D:\MyDocuments\python\work\TestPy2Exe\build
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\collect-2.5
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\bundle-2.5
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\temp
creating D:\MyDocuments\python\work\TestPy2Exe\dist
*** searching for required modules ***
*** parsing results ***
creating python loader for extension 'unicodedata'
creating python loader for extension 'bz2'
*** finding dlls needed ***
*** create binaries ***
*** byte compile python files ***
byte-compiling C:\Python25\lib\StringIO.py to StringIO.pyc
byte-compiling C:\Python25\lib\UserDict.py to UserDict.pyc
byte-compiling C:\Python25\lib\atexit.py to atexit.pyc
byte-compiling C:\Python25\lib\base64.py to base64.pyc
byte-compiling C:\Python25\lib\codecs.py to codecs.pyc
byte-compiling C:\Python25\lib\copy.py to copy.pyc
byte-compiling C:\Python25\lib\copy_reg.py to copy_reg.pyc
byte-compiling C:\Python25\lib\encodings\__init__.py to encodings\__init__.pyc
creating D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\collect-2.5\encodings
byte-compiling C:\Python25\lib\encodings\aliases.py to encodings\aliases.pyc
byte-compiling C:\Python25\lib\encodings\ascii.py to encodings\ascii.pyc
byte-compiling C:\Python25\lib\encodings\base64_codec.py to encodings\base64_codec.pyc
byte-compiling C:\Python25\lib\encodings\big5.py to encodings\big5.pyc
byte-compiling C:\Python25\lib\encodings\big5hkscs.py to encodings\big5hkscs.pyc
byte-compiling C:\Python25\lib\encodings\bz2_codec.py to encodings\bz2_codec.pyc
byte-compiling C:\Python25\lib\encodings\charmap.py to encodings\charmap.pyc
byte-compiling C:\Python25\lib\encodings\cp037.py to encodings\cp037.pyc
byte-compiling C:\Python25\lib\encodings\cp1006.py to encodings\cp1006.pyc
byte-compiling C:\Python25\lib\encodings\cp1026.py to encodings\cp1026.pyc
byte-compiling C:\Python25\lib\encodings\cp1140.py to encodings\cp1140.pyc
byte-compiling C:\Python25\lib\encodings\cp1250.py to encodings\cp1250.pyc
byte-compiling C:\Python25\lib\encodings\cp1251.py to encodings\cp1251.pyc
byte-compiling C:\Python25\lib\encodings\cp1252.py to encodings\cp1252.pyc
byte-compiling C:\Python25\lib\encodings\cp1253.py to encodings\cp1253.pyc
byte-compiling C:\Python25\lib\encodings\cp1254.py to encodings\cp1254.pyc
byte-compiling C:\Python25\lib\encodings\cp1255.py to encodings\cp1255.pyc
byte-compiling C:\Python25\lib\encodings\cp1256.py to encodings\cp1256.pyc
byte-compiling C:\Python25\lib\encodings\cp1257.py to encodings\cp1257.pyc
byte-compiling C:\Python25\lib\encodings\cp1258.py to encodings\cp1258.pyc
byte-compiling C:\Python25\lib\encodings\cp424.py to encodings\cp424.pyc
byte-compiling C:\Python25\lib\encodings\cp437.py to encodings\cp437.pyc
byte-compiling C:\Python25\lib\encodings\cp500.py to encodings\cp500.pyc
byte-compiling C:\Python25\lib\encodings\cp737.py to encodings\cp737.pyc
byte-compiling C:\Python25\lib\encodings\cp775.py to encodings\cp775.pyc
byte-compiling C:\Python25\lib\encodings\cp850.py to encodings\cp850.pyc
byte-compiling C:\Python25\lib\encodings\cp852.py to encodings\cp852.pyc
byte-compiling C:\Python25\lib\encodings\cp855.py to encodings\cp855.pyc
byte-compiling C:\Python25\lib\encodings\cp856.py to encodings\cp856.pyc
byte-compiling C:\Python25\lib\encodings\cp857.py to encodings\cp857.pyc
byte-compiling C:\Python25\lib\encodings\cp860.py to encodings\cp860.pyc
byte-compiling C:\Python25\lib\encodings\cp861.py to encodings\cp861.pyc
byte-compiling C:\Python25\lib\encodings\cp862.py to encodings\cp862.pyc
byte-compiling C:\Python25\lib\encodings\cp863.py to encodings\cp863.pyc
byte-compiling C:\Python25\lib\encodings\cp864.py to encodings\cp864.pyc
byte-compiling C:\Python25\lib\encodings\cp865.py to encodings\cp865.pyc
byte-compiling C:\Python25\lib\encodings\cp866.py to encodings\cp866.pyc
byte-compiling C:\Python25\lib\encodings\cp869.py to encodings\cp869.pyc
byte-compiling C:\Python25\lib\encodings\cp874.py to encodings\cp874.pyc
byte-compiling C:\Python25\lib\encodings\cp875.py to encodings\cp875.pyc
byte-compiling C:\Python25\lib\encodings\cp932.py to encodings\cp932.pyc
byte-compiling C:\Python25\lib\encodings\cp949.py to encodings\cp949.pyc
byte-compiling C:\Python25\lib\encodings\cp950.py to encodings\cp950.pyc
byte-compiling C:\Python25\lib\encodings\euc_jis_2004.py to encodings\euc_jis_2004.pyc
byte-compiling C:\Python25\lib\encodings\euc_jisx0213.py to encodings\euc_jisx0213.pyc
byte-compiling C:\Python25\lib\encodings\euc_jp.py to encodings\euc_jp.pyc
byte-compiling C:\Python25\lib\encodings\euc_kr.py to encodings\euc_kr.pyc
byte-compiling C:\Python25\lib\encodings\gb18030.py to encodings\gb18030.pyc
byte-compiling C:\Python25\lib\encodings\gb2312.py to encodings\gb2312.pyc
byte-compiling C:\Python25\lib\encodings\gbk.py to encodings\gbk.pyc
byte-compiling C:\Python25\lib\encodings\hex_codec.py to encodings\hex_codec.pyc
byte-compiling C:\Python25\lib\encodings\hp_roman8.py to encodings\hp_roman8.pyc
byte-compiling C:\Python25\lib\encodings\hz.py to encodings\hz.pyc
byte-compiling C:\Python25\lib\encodings\idna.py to encodings\idna.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp.py to encodings\iso2022_jp.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp_1.py to encodings\iso2022_jp_1.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp_2.py to encodings\iso2022_jp_2.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp_2004.py to encodings\iso2022_jp_2004.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp_3.py to encodings\iso2022_jp_3.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_jp_ext.py to encodings\iso2022_jp_ext.pyc
byte-compiling C:\Python25\lib\encodings\iso2022_kr.py to encodings\iso2022_kr.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_1.py to encodings\iso8859_1.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_10.py to encodings\iso8859_10.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_11.py to encodings\iso8859_11.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_13.py to encodings\iso8859_13.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_14.py to encodings\iso8859_14.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_15.py to encodings\iso8859_15.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_16.py to encodings\iso8859_16.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_2.py to encodings\iso8859_2.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_3.py to encodings\iso8859_3.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_4.py to encodings\iso8859_4.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_5.py to encodings\iso8859_5.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_6.py to encodings\iso8859_6.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_7.py to encodings\iso8859_7.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_8.py to encodings\iso8859_8.pyc
byte-compiling C:\Python25\lib\encodings\iso8859_9.py to encodings\iso8859_9.pyc
byte-compiling C:\Python25\lib\encodings\johab.py to encodings\johab.pyc
byte-compiling C:\Python25\lib\encodings\koi8_r.py to encodings\koi8_r.pyc
byte-compiling C:\Python25\lib\encodings\koi8_u.py to encodings\koi8_u.pyc
byte-compiling C:\Python25\lib\encodings\latin_1.py to encodings\latin_1.pyc
byte-compiling C:\Python25\lib\encodings\mac_arabic.py to encodings\mac_arabic.pyc
byte-compiling C:\Python25\lib\encodings\mac_centeuro.py to encodings\mac_centeuro.pyc
byte-compiling C:\Python25\lib\encodings\mac_croatian.py to encodings\mac_croatian.pyc
byte-compiling C:\Python25\lib\encodings\mac_cyrillic.py to encodings\mac_cyrillic.pyc
byte-compiling C:\Python25\lib\encodings\mac_farsi.py to encodings\mac_farsi.pyc
byte-compiling C:\Python25\lib\encodings\mac_greek.py to encodings\mac_greek.pyc
byte-compiling C:\Python25\lib\encodings\mac_iceland.py to encodings\mac_iceland.pyc
byte-compiling C:\Python25\lib\encodings\mac_latin2.py to encodings\mac_latin2.pyc
byte-compiling C:\Python25\lib\encodings\mac_roman.py to encodings\mac_roman.pyc
byte-compiling C:\Python25\lib\encodings\mac_romanian.py to encodings\mac_romanian.pyc
byte-compiling C:\Python25\lib\encodings\mac_turkish.py to encodings\mac_turkish.pyc
byte-compiling C:\Python25\lib\encodings\mbcs.py to encodings\mbcs.pyc
byte-compiling C:\Python25\lib\encodings\palmos.py to encodings\palmos.pyc
byte-compiling C:\Python25\lib\encodings\ptcp154.py to encodings\ptcp154.pyc
byte-compiling C:\Python25\lib\encodings\punycode.py to encodings\punycode.pyc
byte-compiling C:\Python25\lib\encodings\quopri_codec.py to encodings\quopri_codec.pyc
byte-compiling C:\Python25\lib\encodings\raw_unicode_escape.py to encodings\raw_unicode_escape.pyc
byte-compiling C:\Python25\lib\encodings\rot_13.py to encodings\rot_13.pyc
byte-compiling C:\Python25\lib\encodings\shift_jis.py to encodings\shift_jis.pyc
byte-compiling C:\Python25\lib\encodings\shift_jis_2004.py to encodings\shift_jis_2004.pyc
byte-compiling C:\Python25\lib\encodings\shift_jisx0213.py to encodings\shift_jisx0213.pyc
byte-compiling C:\Python25\lib\encodings\string_escape.py to encodings\string_escape.pyc
byte-compiling C:\Python25\lib\encodings\tis_620.py to encodings\tis_620.pyc
byte-compiling C:\Python25\lib\encodings\undefined.py to encodings\undefined.pyc
byte-compiling C:\Python25\lib\encodings\unicode_escape.py to encodings\unicode_escape.pyc
byte-compiling C:\Python25\lib\encodings\unicode_internal.py to encodings\unicode_internal.pyc
byte-compiling C:\Python25\lib\encodings\utf_16.py to encodings\utf_16.pyc
byte-compiling C:\Python25\lib\encodings\utf_16_be.py to encodings\utf_16_be.pyc
byte-compiling C:\Python25\lib\encodings\utf_16_le.py to encodings\utf_16_le.pyc
byte-compiling C:\Python25\lib\encodings\utf_7.py to encodings\utf_7.pyc
byte-compiling C:\Python25\lib\encodings\utf_8.py to encodings\utf_8.pyc
byte-compiling C:\Python25\lib\encodings\utf_8_sig.py to encodings\utf_8_sig.pyc
byte-compiling C:\Python25\lib\encodings\uu_codec.py to encodings\uu_codec.pyc
byte-compiling C:\Python25\lib\encodings\zlib_codec.py to encodings\zlib_codec.pyc
byte-compiling C:\Python25\lib\getopt.py to getopt.pyc
byte-compiling C:\Python25\lib\linecache.py to linecache.pyc
byte-compiling C:\Python25\lib\macpath.py to macpath.pyc
byte-compiling C:\Python25\lib\ntpath.py to ntpath.pyc
byte-compiling C:\Python25\lib\os.py to os.pyc
byte-compiling C:\Python25\lib\os2emxpath.py to os2emxpath.pyc
byte-compiling C:\Python25\lib\popen2.py to popen2.pyc
byte-compiling C:\Python25\lib\posixpath.py to posixpath.pyc
byte-compiling C:\Python25\lib\quopri.py to quopri.pyc
byte-compiling C:\Python25\lib\re.py to re.pyc
byte-compiling C:\Python25\lib\repr.py to repr.pyc
byte-compiling C:\Python25\lib\sre.py to sre.pyc
byte-compiling C:\Python25\lib\sre_compile.py to sre_compile.pyc
byte-compiling C:\Python25\lib\sre_constants.py to sre_constants.pyc
byte-compiling C:\Python25\lib\sre_parse.py to sre_parse.pyc
byte-compiling C:\Python25\lib\stat.py to stat.pyc
byte-compiling C:\Python25\lib\string.py to string.pyc
byte-compiling C:\Python25\lib\stringprep.py to stringprep.pyc
byte-compiling C:\Python25\lib\struct.py to struct.pyc
byte-compiling C:\Python25\lib\traceback.py to traceback.pyc
byte-compiling C:\Python25\lib\types.py to types.pyc
byte-compiling C:\Python25\lib\warnings.py to warnings.pyc
byte-compiling D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\temp\bz2.py to bz2.pyc
byte-compiling D:\MyDocuments\python\work\TestPy2Exe\build\bdist.win32\winexe\temp\unicodedata.py to unicodedata.pyc
*** copy extensions ***
copying C:\Python25\DLLs\bz2.pyd -> D:\MyDocuments\python\work\TestPy2Exe\dist
copying C:\Python25\DLLs\unicodedata.pyd -> D:\MyDocuments\python\work\TestPy2Exe\dist
*** copy dlls ***
copying C:\WINDOWS\system32\MSVCR71.dll -> D:\MyDocuments\python\work\TestPy2Exe\dist
copying C:\Python25\MSVCR71.dll -> D:\MyDocuments\python\work\TestPy2Exe\dist
copying C:\Python25\w9xpopen.exe -> D:\MyDocuments\python\work\TestPy2Exe\dist
copying C:\WINDOWS\system32\python25.dll -> D:\MyDocuments\python\work\TestPy2Exe\dist
setting sys.winver for 'D:\MyDocuments\python\work\TestPy2Exe\dist\python25.dll' to 'py2exe'
copying C:\Python25\lib\site-packages\py2exe\run.exe -> D:\MyDocuments\python\work\TestPy2Exe\dist\simple.exe

*** binary dependencies ***
Your executable(s) also depend on these dlls which are not included,
you may or may not need to distribute them.

Make sure you have the license if you distribute any of them, and
make sure you don't distribute files belonging to the operating system.

   ADVAPI32.dll - C:\WINDOWS\system32\ADVAPI32.dll
   USER32.dll - C:\WINDOWS\system32\USER32.dll
   SHELL32.dll - C:\WINDOWS\system32\SHELL32.dll
   KERNEL32.dll - C:\WINDOWS\system32\KERNEL32.dll
또한 같은 디랙토리에 dist, build 디랙토리가 생성된다.

최종 변환된 simple.exe 파일은 dist 디랙토리 내에 있으며, python이 설치되지 않은 컴퓨터에서 실행하기 위해서는 dist 디랙토리 내의 파일들 전체가 필요하다. 간단하게는 이 디랙토리의 파일들을 압축해서 전달할 수도 있고, 별도의 installer를 사용해서 패키징 해서 전달하면 좀 더 깔끔할 것이다.


변환 로그의 마지막 부분을 보면 푸른색으로 표시한 dist 디랙토리에 포함되지 않지만 이 .exe 파일을 실행할 때 필요한 DLL 파일들의 목록을 볼 수 있다. 즉 dist 디랙토리만 가져다 다른 PC에서 실행했을 때 그 PC에 위 DLL파일이 없다면 실행되지않는다는 뜻이다.



Troubleshooting
다양한 python module들을 사용하는 스크립트를 .exe로 변환할 때에는 잘 안되는 경우가 많다. 주로 사용하는 모듈이 복잡한 경우거나 여러 모듈들에서 symbol name이 충돌하는 경우가 그렇다. py2exe는 단일 namespace를 사용하기 때문에 스크립트가 사용하는 여러 모듈 들 중 동일한 symbol name이 있는 경우 그들을 구분하지 못한다.

자주 사용하는 모듈을 대상으로 몇가지 스크립트들을 변환해본 결과 wxPython은 변환이 잘 되고, matplotlib는 거의 안된다. http://www.py2exe.org/index.cgi/WorkingWithVariousPackagesAndModules 에서 몇가지 모듈들을 변환할때 참고할 수 있는 내용을 찾을 수 있다.


wxPython을 사용하는 간단한 스크립트를 변환한 예 : a_star.py




저작권

아주 간단한 python 스크립트일지라도 .exe로 변환하면 부가적인 파일들이 많이 붙게 된다. 아무리 적어도 최종 파일은 수메가바이트 정도의 사이즈를 가지게 되고, 다양한 모듈을 사용한다면 수십메가바이트 이상이 되기도 한다. 이것은 python interpreter와 각 모듈 DLL들이 포함되기 때문이다. 사이즈 문제는 감수한다 하더라도 포함되는 DLL들의 저작권 문제는 고려해야만 한다. 즉 내가 만든 python 스크립트를 .exe 형태로 배포하려는 의도이지만 그 과정에서 다른 DLL들이 포함되는 것이 문제가 될 수 있다는 것이다.


변환 로그의 마지막에 있는, dist 디랙토리에 포함되지는 않지만 필요한 DLL 리스트 역시 주의해서 참고해야 한다. 이 DLL들은 어지간한 경우에는 있는것이 보편적이지만, 이 DLL들이 없는 PC가 있을 수도 있다. 그런 경우에는 해당 DLL을 인터넷에서 직접 찾아서 다운로드해서 사용하도록 유도하는 방법을 쓰거나, 해당 DLL들을 별도로 패키징 해서 전달하는 방법도 있다. 후자인 경우 저작권 문제를 심각하게 고려해야 한다.



배포본 만들기

py2exe는 python 스크립트를 .exe 형태로 만들어 주는 기능만 가지고 있다. 그리고 만들어진 파일은 하나의 .exe 파일이 아니라 여러개의(경우에 따라 수십~수백개의) 파일로 구성된다. 이런 형태로는 배포하기 적당하지 않기 때문에 별도의 installer등을 이용하는 것이 깔끔하다.

이럴때 사용하는 것이 NSISInno Setup과 같은 installer이다.