A Windows Launcher Tutorial

Some readers familiar with Unix scripting may find the prior section enough to get started. For others, this section provides additional context in the form of a tutorial, which gives concrete examples of the launcher in action for you to trace through. This section also discloses additional launcher details along the way, though, so even well-seasoned Unix veterans may benefit from a quick scan here before FTPing all their Python scripts to the local Windows box.

To get started, we’ll be using the following simple script, what.py, which can be run under both 2.X and 3.X to echo the version number of the Python that runs its code. It uses sys.version—a string whose first component after splitting on whitespace is Python’s version number:

#!python3
import sys
print(sys.version.split()[0])     # First part of string

If you want to work along, type this script’s code in your favorite text file editor, open a Command Prompt window for typing the command lines we’ll be running, and cd to the directory where you’ve save the script (C:\code is where I’m working, but feel free to save this wherever you wish, and see Chapter 3 for more Windows usage pointers).

This script’s first-line comment serves to designate the required Python version; it must begin with #! per Unix convention, and allows for a space before the python3 or not. On my machine I currently have Pythons 2.7, 3,1, 3.2, and 3.3 all installed; let’s watch which version is invoked as the script’s first line is modified in the following sections, exploring file directives, command lines, and defaults along the way.

Step 1: Using Version Directives in Files

As this script is coded, when run by icon click or command line, the first line directs the registered py.exe launcher to run using the latest 3.X installed:

#! python3
import sys
print(sys.version.split()[0])

C:\code> what.py                  # Run per file directive
3.3.0

C:\code> py what.py               # Ditto: latest 3.X
3.3.0

Again, the space after #! is optional; I added a space to demonstrate the point here. Note that the first what.py command here is equivalent to both an icon click and a full py what.py, because the py.exe program is registered to open .py files automatically in the Windows filename associations registry when the launcher is installed.

Also note that when launcher documentation (including this appendix) talks about the latest version, it means the highest-numbered version. That is, it refers to the latest released, not the latest installed on your computer (e.g., if you install 3.1 after 3.3, #!python3 selects the latter). The launcher cycles through the Pythons on your computer to find the highest-numbered version that matches your specification or defaults; this differs from the former last-installed-wins model.

Now, changing the first line name to python2 triggers the latest (really, highest-numbered) 2.X installed instead. Here’s this change at work; I’ll omit the last two lines of our script from this point on because they won’t be altered:

#! python2
...rest of script unchanged

C:\code> what.py                  # Run with latest 2.X per #!
2.7.3

And you can request a more specific version if needed—for example, if you don’t want the latest in a Python line:

#! python3.1
...

C:\code> what.py                  # Run with 3.1 per #!
3.1.4

This is true even if the requested version is not installed—which is treated as an error case by the launcher:

#! python2.6
...

C:\code> what.py
Requested Python version (2.6) is not installed

Unrecognized Unix #! lines are also treated as errors, unless you give a version number as a command-line switch to compensate, as the next section describes in more detail (and as the section on launcher issues will revisit as a pitfall):

#!/bin/python
...

C:\code> what.py
Unable to create process using '/bin/python "C:\code\what.py" '

C:\code> py what.py
Unable to create process using '/bin/python what.py'

C:\code> py −3 what.py
3.3.0

Technically, the launcher recognizes Unix-style #! lines at the top of script files that follow one of the following four patterns:

#!/usr/bin/env python*
#!/usr/bin/python*
#!/usr/local/bin/python*
#!python*

Any #! line that does not take one of these recognized and parseable forms is assumed to be a fully specified command line to start a process to run the file, which is passed to Windows as is, and generates the error message we saw previously if it is not a valid Windows command. (The launcher also supports “customized” command expansions via its configuration files, which are attempted before passing unrecognized commands on to Windows, but we’ll gloss over these here.)

In recognizable #! lines, directory paths are coded per Unix convention, for portability to that platform. The * part at the end of the four preceding recognized patterns denotes an optional Python version number, in one of three forms:

Partial (e.g., python3)

To run the version installed with the highest minor release number among those with the major release number given

Full (e.g., python3.1)

To run that specific version only, optionally suffixed by −32 to prefer a 32-bit version (e.g., python3.1-32)

Omitted (e.g., python)

To run the launcher’s default version, which is 2 unless changed (e.g., by setting the PY_PYTHON environment variable to 3), another pitfall described ahead

Files with no #! line at all behave the same as those that name just a generic python—the aforementioned omitted case—and are influenced by PY_PYTHON default settings. The first case, partials, may also be affected by version-specific environment settings (e.g., set PY_PYTHON3 to 3.1 to select 3.1 for python3, and set PY_PYTHON2 to 2.6 to pick 2.6 for python2). We’ll revisit defaults later in this tutorial.

First, though, note that anything after the * part in a #! line’s format is assumed to be command-line arguments to Python itself (i.e., program python.exe), unless you also give arguments in a py command line that are deemed to supersede #! line arguments by the launcher:

#!python3 [any python.exe arguments go here]
...

These include all the Python command-line arguments we met in Appendix A. But this leads us to launcher command lines in general, and will suffice as a natural segue to the next section.

Step 2: Using Command-Line Version Switches

As mentioned, version switches on command lines can be used to select a Python version if one isn’t present in the file. You run a py or pyw command line to pass them a switch this way, instead of relying on filename associations in the registry, and instead of (or in addition to) giving versions in #! lines in files. In the following, we modify our script so that it has no #! directive:

# not a launcher directive
...

C:\code> py −3 what.py           # Run per command-line switch
3.3.0

C:\code> py −2 what.py           # Ditto: latest 2.X installed
2.7.3

C:\code> py −3.2 what.py         # Ditto: 3.2 specifically (and only)
3.2.3

C:\code> py what.py              # Run per launcher's default (ahead)
2.7.3

But command-line switches also take precedence over a version designation in a file’s directive:

#! python3.1
...

C:\code> what.py                 # Run per file directive
3.1.4

C:\code> py what.py              # Ditto
3.1.4

C:\code> py −3.2 what.py         # Switches override directives
3.2.3

C:\code> py −2 what.py           # Ditto
2.7.3

Formally, the launcher accepts the following command-line argument types (which exactly mirror the * part at the end of a file’s #! line described in the prior section):

−2         Launch the latest Python 2.X version
-3         Launch the latest Python 3.X version
-X.Y       Launch the specified Python version (X is 2 or 3)
-X.Y−32    Launch the specified 32-bit Python version

And the launcher’s command lines take the following general form:

py [py.exe arg] [python.exe args] script.py [script.py args]

Anything following the launcher’s own argument (if present) is treated as though it were passed to the python.exe program—typically, this includes any arguments for Python itself, followed by the script filename, followed by any arguments meant for the script.

The usual -m mod, -c cmd, and - program specification forms work in a py command line too, as do all the other Python command-line arguments covered in Appendix A. As mentioned earlier, arguments to python.exe can also appear at the end of the #! directive line in a file, if used, though arguments in py command lines override them.

To see how this works, let’s write a new script that extends the prior to display command-line arguments; sys.argv is the script’s own arguments, and I’m using the Python (python.exe) -i switch, which directs it to the interactive prompt (>>>) after a script runs:

# args.py, show my arguments too
import sys
print(sys.version.split()[0])
print(sys.argv)

C:\code> py −3 -i args.py -a 1 -b -c     # −3: py, -i: python, rest: script
3.3.0
['args.py', '-a', '1', '-b', '-c']
>>> ^Z

C:\code> py -i args.py -a 1 -b -c        # Args to python, script
2.7.3
['args.py', '-a', '1', '-b', '-c']
>>> ^Z

C:\code> py −3 -c print(99)              # −3 to py, rest to python: "-c cmd"
99

C:\code> py −2 -c "print 99"
99

Notice how the first two launches run the default Python unless a version is given in the command line, because no #! line appears in the script itself. Somewhat coincidentally, that leads us to the last topic of this tutorial.

Step 3: Using and Changing Defaults

As also mentioned, the launcher defaults to 2.X for a generic python in a #! directive with no specific version number. This is true whether this generic form appears in a full Unix path (e.g., #!/usr/bin/python) or not (#!python). Here’s the latter case in action, coded in our original what.py script:

#!python
...                           # Same as #!/usr/bin/python

C:\code> what.py              # Run per launcher default
2.7.3

The default is also applied when no directive is present at all—perhaps the most common case for code written to be used on Windows primarily or exclusively:

# not a launcher directive
...

C:\code> what.py              # Also run per default
2.7.3

C:\code> py what.py           # Ditto
2.7.3

But you can set the launcher’s default to 3.X with initialization file or environment variable settings, which will apply to both files run from command lines and by icon clicks via their name’s association with py.exe or pyw.exe in the Windows registry:

# not a launcher directive
...

C:\code> what.py               # Run per default
2.7.3

C:\code> set PY_PYTHON=3       # Or via Control Panel/System
C:\code> what.py               # Run per changed default
3.3.0

As suggested earlier, for more fine-grained control you can also set version-specific environment variables to direct partial selections to a specific release, instead of falling back on the installed release with the highest minor number:

#!python3
...

C:\code> py what.py             # Runs "latest" 3.X
3.3.0

C:\code> set PY_PYTHON3=3.1     # Use PY_PYTHON2 for 2.X
C:\code> py what.py             # Override highest-minor choice
3.1.4

The set used in these interactions applies to its Command Prompt window only; making such settings in the Control Panel’s System window will make them apply globally across your machine (see Appendix A for help with these settings). You may or may not want to set defaults this way depending on the majority of the Python code you’ll be running. Many Python 2.X users can probably rely on defaults unchanged, and override them in #! lines or py command lines as needed.

However, the setting used for directive-less files, PY_PYTHON, seems fairly crucial. Most programmers who have used Python on Windows in the past will probably expect 3.X to be the default after installing 3.3, especially given that the launcher is installed by 3.3 in the first place—a seeming paradox, which leads us to the next section.