Distribution using a Python embeddable zip file
0 Why this note?
The problem here is about distribution of a Python application. Why, on earth, do you have to depend on tools, like PyInstaller? There could be several reasons such as: It must be self-contained, i.e., it must contain all it needs to run. The distribution file size should be smaller, i.e., it should contain only what it depends on. The application should be started with an executable file, namely, a .exe file. The search path for the distributed DLLs or the modules must be correctly configured so that they can be found without confusion. The python initialization (sys.argv, etc.) should be sufficient. Here are some notes collected in my trial to make a distribution, without any special tools, from an embeddable zip file for Python 7.3.4 on Windows.1 Technical Points
1.1 Downloading an embeddable zip file
You can download one at the official download page. The following descriptions assume that you have unzipped the zip file to a folder named "embeddables".1.2 Installing pip
Get a "get-pip.py" script and run it in the unzipped folder. > cd embeddables > python.exe get-pip.py This will install pip into the "Lib/site-packages" folder. However, it is not yet ready since the folder is not included in the default search path configuration of the python.exe in the embeddable zip file.1.3 Adding to the search path
To add the "Lib/site-packages" together with some other required folders to the search path of python.exe, edit a path configuration file named "python37._pth". default python37._pth Python python37.zip . # Uncomment to run site.main() automatically # import site Add what you need to the above default content, for example, as follows. modified python37._pth Python python37.zip . .\Lib .\DLLs # Uncomment to run site.main() automatically import site 1.4 Installing required modules Install required modules using pip. > python.exe -m pip install module_name or wheel_file 1 > python.exe -m pip install module_name or wheel_file 1.5 Copying the tkinter module (if needed) You can't pip install tkinter. Copy the folders/files below into the "embeddables" folder from the normal Python installation. (See also the references given later) tcl Lib/tkinter DLLs/_tkinter.pyd, tcl86t.dll, tk86t.dll 1.6 Copying the folders required in development The following folders will be required to make a starter executable written in C. Copy them from a normal Python installation, which may have been created, for example, from an executable installer. "include" which contain C header files "libs" which contain ".lib" files such as "python37.lib" 1.7 Building a starter executable An example C code below, given in "Very High Level Embedding" in the official document might not suffice from, for example, the following reasons. sys.argv in the embedded Python is empty, which implies, for example, even the starter program name is not given in sys.argv[0]. It would be easier if the bootstrapping Python code was given in a Python script file. Such a script file should be located relative to the starter executable. embedding example C++ #define PY_SSIZE_T_CLEAN #includeint main(int argc, char *argv[]) { wchar_t *program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } Py_SetProgramName(program); /* optional but recommended */ Py_Initialize(); PyRun_SimpleString("from time import time,ctime\n" "print('Today is', ctime(time()))\n"); if (Py_FinalizeEx() < 0) { exit(120); } PyMem_RawFree(program); return 0; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define PY_SSIZE_T_CLEAN #include int main(int argc, char *argv[]) { wchar_t *program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } Py_SetProgramName(program); /* optional but recommended */ Py_Initialize(); PyRun_SimpleString("from time import time,ctime\n" "print('Today is', ctime(time()))\n"); if (Py_FinalizeEx() < 0) { exit(120); } PyMem_RawFree(program); return 0; } Suppose you have a Python script as below to be invoked from the C code. example.py Python import sys print("sys.argv=", sys.argv) 1 2 import sys print("sys.argv=", sys.argv) The following code exemplifies how to cover the above inconvenience. Note that the executable built from the code is supposed to be placed in the "embeddables" folder to be able to load the associated DLLs ("python37.dll", etc.). embedding example (modified) C++ #include #define PY_SSIZE_T_CLEAN #include int main(int argc, char *argv[]) { wchar_t* program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } Py_SetProgramName(program); /* optional but recommended */ FILE* fp = fopen("..\\build\\example.py", "r"); if (fp == 0) { printf("Script path error: %s\n", strerror(errno)); return 1; } std::vector wargv; for (int i = 0; i < argc; ++i) { wargv.push_back(Py_DecodeLocale(argv[i], NULL)); } Py_Initialize(); PySys_SetArgv(argc, &wargv[0]); PyRun_SimpleFile(fp, "example.py"); if (Py_FinalizeEx() < 0) { exit(120); } PyMem_RawFree(program); for (int i = 0; i < argc; ++i) { PyMem_RawFree(wargv[i]); } return 0; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include #define PY_SSIZE_T_CLEAN #include int main(int argc, char *argv[]) { wchar_t* program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } Py_SetProgramName(program); /* optional but recommended */ FILE* fp = fopen("..\\build\\example.py", "r"); if (fp == 0) { printf("Script path error: %s\n", strerror(errno)); return 1; } std::vector wargv; for (int i = 0; i < argc; ++i) { wargv.push_back(Py_DecodeLocale(argv[i], NULL)); } Py_Initialize(); PySys_SetArgv(argc, &wargv[0]); PyRun_SimpleFile(fp, "example.py"); if (Py_FinalizeEx() < 0) { exit(120); } PyMem_RawFree(program); for (int i = 0; i < argc; ++i) { PyMem_RawFree(wargv[i]); } return 0; } 2 References Python Embeddable Zip File Doesn't Include lib/site-packages in sys.path Python embeddable zip: install Tkinter Compile CPython on Windows Embedding python into C++ program rcedit - Command line tool to edit resources of exe file on Windows.