@@ -207,6 +207,12 @@ fits in memory::
207
207
>>> with open('myapp.pyz', 'wb') as f:
208
208
>>> f.write(temp.getvalue())
209
209
210
+
211
+ .. _zipapp-specifying-the-interpreter :
212
+
213
+ Specifying the Interpreter
214
+ --------------------------
215
+
210
216
Note that if you specify an interpreter and then distribute your application
211
217
archive, you need to ensure that the interpreter used is portable. The Python
212
218
launcher for Windows supports most common forms of POSIX ``#! `` line, but there
@@ -223,6 +229,169 @@ are other issues to consider:
223
229
exact version like "/usr/bin/env python3.4" as you will need to change your
224
230
shebang line for users of Python 3.5, for example.
225
231
232
+ Typically, you should use an "/usr/bin/env python2" or "/usr/bin/env python3",
233
+ depending on whether your code is written for Python 2 or 3.
234
+
235
+
236
+ Creating Standalone Applications with zipapp
237
+ --------------------------------------------
238
+
239
+ Using the :mod: `zipapp ` module, it is possible to create self-contained Python
240
+ programs, which can be distributed to end users who only need to have a
241
+ suitable version of Python installed on their system. The key to doing this
242
+ is to bundle all of the application's dependencies into the archive, along
243
+ with the application code.
244
+
245
+ The steps to create a standalone archive are as follows:
246
+
247
+ 1. Create your application in a directory as normal, so you have a ``myapp ``
248
+ directory containing a ``__main__.py `` file, and any supporting application
249
+ code.
250
+
251
+ 2. Install all of your application's dependencies into the ``myapp `` directory,
252
+ using pip:
253
+
254
+ .. code-block :: sh
255
+
256
+ $ python -m pip install -r requirements.txt --target myapp
257
+
258
+ (this assumes you have your project requirements in a ``requirements.txt ``
259
+ file - if not, you can just list the dependencies manually on the pip command
260
+ line).
261
+
262
+ 3. Optionally, delete the ``.dist-info `` directories created by pip in the
263
+ ``myapp `` directory. These hold metadata for pip to manage the packages, and
264
+ as you won't be making any further use of pip they aren't required -
265
+ although it won't do any harm if you leave them.
266
+
267
+ 4. Package the application using:
268
+
269
+ .. code-block :: sh
270
+
271
+ $ python -m zipapp -p " interpreter" myapp
272
+
273
+ This will produce a standalone executable, which can be run on any machine with
274
+ the appropriate interpreter available. See :ref: `zipapp-specifying-the-interpreter `
275
+ for details. It can be shipped to users as a single file.
276
+
277
+ On Unix, the ``myapp.pyz `` file is executable as it stands. You can rename the
278
+ file to remove the ``.pyz `` extension if you prefer a "plain" command name. On
279
+ Windows, the ``myapp.pyz[w] `` file is executable by virtue of the fact that
280
+ the Python interpreter registers the ``.pyz `` and ``.pyzw `` file extensions
281
+ when installed.
282
+
283
+
284
+ Making a Windows executable
285
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
286
+
287
+ On Windows, registration of the ``.pyz `` extension is optional, and
288
+ furthermore, there are certain places that don't recognise registered
289
+ extensions "transparently" (the simplest example is that
290
+ ``subprocess.run(['myapp']) `` won't find your application - you need to
291
+ explicitly specify the extension).
292
+
293
+ On Windows, therefore, it is often preferable to create an executable from the
294
+ zipapp. This is relatively easy, although it does require a C compiler. The
295
+ basic approach relies on the fact that zipfiles can have arbitrary data
296
+ prepended, and Windows exe files can have arbitrary data appended. So by
297
+ creating a suitable launcher and t
8000
acking the ``.pyz `` file onto the end of it,
298
+ you end up with a single-file executable that runs your application.
299
+
300
+ A suitable launcher can be as simple as the following::
301
+
302
+ #define Py_LIMITED_API 1
303
+ #include "Python.h"
304
+
305
+ #define WIN32_LEAN_AND_MEAN
306
+ #include <windows.h>
307
+
308
+ #ifdef WINDOWS
309
+ int WINAPI wWinMain(
310
+ HINSTANCE hInstance, /* handle to current instance */
311
+ HINSTANCE hPrevInstance, /* handle to previous instance */
312
+ LPWSTR lpCmdLine, /* pointer to command line */
313
+ int nCmdShow /* show state of window */
314
+ )
315
+ #else
316
+ int wmain()
317
+ #endif
318
+ {
319
+ wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
320
+ myargv[0] = __wargv[0];
321
+ memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
322
+ return Py_Main(__argc+1, myargv);
323
+ }
324
+
325
+ If you define the ``WINDOWS `` preprocessor symbol, this will generate a
326
+ GUI executable, and without it, a console executable.
327
+
328
+ To compile the executable, you can either just use the standard MSVC
329
+ command line tools, or you can take advantage of the fact that distutils
330
+ knows how to compile Python source::
331
+
332
+ >>> from distutils.ccompiler import new_compiler
333
+ >>> import distutils.sysconfig
334
+ >>> import sys
335
+ >>> import os
336
+ >>> from pathlib import Path
337
+
338
+ >>> def compile(src):
339
+ >>> src = Path(src)
340
+ >>> cc = new_compiler()
341
+ >>> exe = src.stem
342
+ >>> cc.add_include_dir(distutils.sysconfig.get_python_inc())
343
+ >>> cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
344
+ >>> # First the CLI executable
345
+ >>> objs = cc.compile([str(src)])
346
+ >>> cc.link_executable(objs, exe)
347
+ >>> # Now the GUI executable
348
+ >>> cc.define_macro('WINDOWS')
349
+ >>> objs = cc.compile([str(src)])
350
+ >>> cc.link_executable(objs, exe + 'w')
351
+
352
+ >>> if __name__ == "__main__":
353
+ >>> compile("zastub.c")
354
+
355
+ The resulting launcher uses the "Limited ABI", so it will run unchanged with
356
+ any version of Python 3.x. All it needs is for Python (``python3.dll ``) to be
357
+ on the user's ``PATH ``.
358
+
359
+ For a fully standalone distribution, you can distribute the launcher with your
360
+ application appended, bundled with the Python "embedded" distribution. This
361
+ will run on any PC with the appropriate architecture (32 bit or 64 bit).
362
+
363
+
364
+ Caveats
365
+ ~~~~~~~
366
+
367
+ There are some limitations to the process of bundling your application into
368
+ a single file. In most, if not all, cases they can be addressed without
369
+ needing major changes to your application.
370
+
371
+ 1. If your application depends on a package that includes a C extension, that
372
+ package cannot be run from a zip file (this is an OS limitation, as executable
373
+ code must be present in the filesystem for the OS loader to load it). In this
374
+ case, you can exclude that dependency from the zipfile, and either require
375
+ your users to have it installed, or ship it alongside your zipfile and add code
376
+ to your ``__main__.py `` to include the directory containing the unzipped
377
+ module in ``sys.path ``. In this case, you will need to make sure to ship
378
+ appropriate binaries for your target architecture(s) (and potentially pick the
379
+ correct version to add to ``sys.path `` at runtime, based on the user's machine).
380
+
381
+ 2. If you are shipping a Windows executable as described above, you either need to
382
+ ensure that your users have ``python3.dll `` on their PATH (which is not the
383
+ default behaviour of the installer) or you should bundle your application with
384
+ the embedded distribution.
385
+
386
+ 3. The suggested launcher above uses the Python embedding API. This means that in
387
+ your application, ``sys.executable `` will be your application, and *not * a
388
+ conventional Python interpreter. Your code and its dependencies need to be
389
+ prepared for this possibility. For example, if your application uses the
390
+ :mod: `multiprocessing ` module, it will need to call
391
+ :func: `multiprocessing.set_executable ` to let the module know where to find the
392
+ standard Python interpreter.
393
+
394
+
226
395
The Python Zip Application Archive Format
227
396
-----------------------------------------
228
397
0 commit comments