The Foxfire Blog

Notes from my past self.

View on GitHub
27 December 2018

Embedded Python for Windows

by Sasha Elaine Fox

Python Desktop App and Windows

The Problem with Interpreters

Deploying desktop applications that have Python components, but are not purely Python packages, is a decidedly fiddley task. Unless the target system is guaranteed to have a Python interpreter installed, as is the case with most Linux distros, the app maintainer will need to find a way to ensure an interpreter is present.

This problem is execrated on Windows system where there are a few competing ways to use Python; we could choose to download Python for Windows which would give us a Python terminal, we could use Anaconda which gives an “Anaconda Prompt”, or we could install Python through Chocolatey which makes Python accessible from Powershell and CMD. Each of these approaches puts the actual interpreter in a different place/assigns a different registry key which makes it difficult for our app to actually find an interpreter on the host system.

To make matters worse programmatic installation of Python/Anaconda without Chocolatey is, at best, janky. While Python and Anaconda support command-line use of their respective installers there isn’t a clean way to programmatically download the latest versions of the installers themselves. We could choose to ship the installers with our package and have whatever installer we happen to be using set things up, but this would mean we are responsible for installer versioning. Unfortunately there is no good analog to apt-get install python on Windows systems.

Options for Deployment

There are a few different approaches we can take to make sure an interpreter is present; we could have our end-user install a Python interpreter along with any packages our app requires, or we should ship an interpreter with our app.

Install Python

Without Chocolatey

# URL and App name will change depending on Python being used.
$url="https://www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe"
$app_name="python-3.7.2-amd64.exe"
$temp_dir=$env:TEMP

Invoke-WebRequest -Uri $url -OutFile $temp_dir\$app_name
Invoke-Expression $temp_dir\$app_name /S /D=%UserProfile%\Python37

With Chocolatey

choco install anaconda

Install Anaconda

Without Chocolatey

# URL and App name will change depending on Python being used.
$url="https://repo.continuum.io/archive/Anaconda3-2018.12-Windows-x86_64.exe"
$app_name="Anaconda3-2018.12-Windows-x86_64.exe"
$temp_dir=$env:TEMP

Invoke-WebRequest -Uri $url -OutFile $temp_dir\$app_name
Invoke-Expression $temp_dir\$app_name /S /D=%UserProfile%\Anaconda3

With Chocolatey

choco install anaconda

Embedded Interpreters

It is not always viable to have an end-user install a Python interpreter; perhaps it would be inappropriate for an interpreter to be present on the host system or the additional complication introduced into installation would be too much of a burden.

Fortunately there is a self-contained Python interpreter designed for embedded use available. This interpreter provides a way to run Python programs without needing to worry about the state of the host system. It is however more limited than a full Python installation, must notably when it comes to package management.

Installation

If we go to the Python for Windows downloads page we’ll see a file labelled “Windows x86-64 embeddable zip file” for various Python version. These files (when unzipped) contain an interpreter along with the Python standard library.

Once this file is unpacked it will contain the main Python interpreter, python.exe and supporting files. The interpreter can be run directly from the folder without any additional installation steps.

Installing Packages

The usual way to handle Python package management is to install all packages through pip. However, as the embedded Python for Windows distribution docs mention:

Using pip to manage dependencies as for a regular Python installation is not supported with this distribution

This raises an obvious question—if we aren’t going to use pip to manage packages then how are we supposed to include packages our app depends on?

If we take a look at the import path an embedded interpreter uses, we see the following.

# Assume we have the Embedded Python 3.6.8 interpreter in our 'Downloads' folder.
~\Downloads\python-3.6.8-embed-amd64\python.exe -c "import sys; print( sys.path )"
['C:\\Users\\SashaFox\\Downloads\\python-3.6.8-embed-amd64\\python36.zip', 'C:\\Users\\SashaFox\\Downloads\\python-3.6.8-embed-amd64']

By default the only path used by the interpreter for imports is in the root directory where the interpreter is located. If we want any packages to be reachable we’ll need to place them in this directory, or add other folders to the path during script execution with sys.path.append(..) or similar.

In order to install packages to this location we will can target option provided by pip, which allows us to pick the directory where packages will be installed. Note this means we will have to have Python installed on our dev/build machine.

For example we wanted to install numpy such that it would be accessible to the interpreter mentioned in the last example we would use the following command.

pip install --target="~\Downloads\python-3.6.8-embed-amd64" numpy
tags: python - windows