A py2exe tutorial – Build a Binary Series!

I received a request to create an article on how to use py2exe and wxPython to create an executable. I decided to do a series on packaging instead. It is my intention to go over the major Windows binary building utilities and show you, dear reader, how to use them to create a binary that you can distribute. Once those articles are done, I’ll show how to use Inno and NSIS. To kick things off, we’ll go over how to use py2exe, probably the most popular of the Windows executable packages.

Let’s Get Started

For this tutorial, we’re going to use a wxPython script that doesn’t do anything. This is a contrived example, but we’re using wx to make it more visually interesting than just doing a console “Hello World” program. Note also that I am using py2exe 0.6.9, wxPython 2.8.11.0 and Python 2.6. Here’s what the end product should look like when run:

Now that we know what it looks like, here’s a look at the code:

import wx

########################################################################
class DemoPanel(wx.Panel):
    """"""
    
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        labels = ["Name", "Address", "City", "State", "Zip",
                  "Phone", "Email", "Notes"]
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        lbl = wx.StaticText(self, label="Please enter your information here:")
        lbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD))
        mainSizer.Add(lbl, 0, wx.ALL, 5)
        for lbl in labels:
            sizer = self.buildControls(lbl)
            mainSizer.Add(sizer, 1, wx.EXPAND)
        self.SetSizer(mainSizer)
        mainSizer.Layout()
        
    #----------------------------------------------------------------------
    def buildControls(self, label):
        """"""
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        size = (80,40)
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD)
        
        lbl = wx.StaticText(self, label=label, size=size)
        lbl.SetFont(font)
        sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
        if label != "Notes":
            txt = wx.TextCtrl(self, name=label)
        else:
            txt = wx.TextCtrl(self, style=wx.TE_MULTILINE, name=label)
        sizer.Add(txt, 1, wx.ALL, 5)
        return sizer
    
    

########################################################################
class DemoFrame(wx.Frame):
    """
    Frame that holds all other widgets
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""        
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          "Py2Exe Tutorial",
                          size=(600,400)
                          )
        panel = DemoPanel(self)        
        self.Show()
        
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = DemoFrame()
    app.MainLoop()

This is fairly straightforward, so I’ll leave it the reader to figure out. This article is about py2exe after all.

The py2exe setup.py file

The key to any py2exe script is the setup.py file. This file controls what gets included or excluded, how much we compress and bundle, and much more! Here is the simplest setup that we can use with the wx script above:

 
from distutils.core import setup
import py2exe

setup(windows=['sampleApp.py'])

As you can see, we import the setup method from distutils.core and then we import py2exe. Next we call setup with a windows keyword parameter and pass it the name of the main file inside a python list object. If you were creating a non-GUI project, than you would use the console key instead of windows. To run this, open up a command prompt and navigate to the appropriate location. Then type “python setup.py py2exe” to run it. This is what I got when I first ran it:

It looks like wxPython requires the “MSVCP90.dll” and Windows can’t find it. A quick Google search yielded the consensus that I needed the “Microsoft Visual C++ 2008 Redistributable Package”, found here. I downloaded it, installed it and tried py2exe again. Same error. This would have probably worked had I been using Visual Studio to create an exe of a C# program. Anyway, the trick was to search the hard drive for the file and then copy it to Python’s DLL folder, which on my machine was found at the following location: C:\Python26\DLLs (adjust as necessary on your machine). Once the DLL was in the proper place, the setup.py file ran just fine. The result was put into a “dist” folder which contains 17 files and weighs in at 15.3 MB. I double-clicked the “sampleApp.exe” file to see if my shiny new binary would work and it did! In older versions of wxPython, you would have needed to include a manifest file to get the right look and feel (i.e the themes), but that was taken care of in 2.8.10 (I think) as was the side-by-side (SxS) assembly manifest file that used to be required.

Note that for non-wxPython scripts, you will probably still need to mess with the SxS manifests and all the hoops that includes. You can read more about that in the py2exe tutorial.

Creating an Advanced setup.py File

Let’s see what other options py2exe gives us for creating binaries by creating a more complex setup.py file.

from distutils.core import setup
import py2exe

includes = []
excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
            'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
            'Tkconstants', 'Tkinter']
packages = []
dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
                'tk84.dll']

setup(
    options = {"py2exe": {"compressed": 2, 
                          "optimize": 2,
                          "includes": includes,
                          "excludes": excludes,
                          "packages": packages,
                          "dll_excludes": dll_excludes,
                          "bundle_files": 3,
                          "dist_dir": "dist",
                          "xref": False,
                          "skip_archive": False,
                          "ascii": False,
                          "custom_boot_script": '',
                         }
              },
    windows=['sampleApp.py']
)

This is pretty self-explanatory, but let’s unpack it anyway. First we set up a few lists that we pass to the options parameter of the setup function.

  • The includes list is for special modules that you need to specifically include. Sometimes py2exe can’t find certain modules, so you get to manually specify them here.
  • The excludes list is a list of which modules to exclude from your program. In this case, we don’t need Tkinter since we’re using wxPython. This list of excludes is what GUI2Exe will exclude by default.
  • The packages list is a list of specific packages to include. Again, sometimes py2exe just can’t find something. I’ve had to include email, PyCrypto, or lxml here before. Note that if the excludes list contains something you’re trying to include in the packages or includes lists, py2exe may continue to exclude them.
  • dll_excludes – excludes dlls that we don’t need in our project.

In the options dictionary, we have a few other options to look at. The compressed key tells py2exe whether or not to compress the zipfile, if it’s set. The optimize key sets the optimization level. Zero is no optimization and 2 is the highest. The bundle_files key bundles dlls in the zipfile or the exe. Valid values for bundle_files are: 3 = don’t bundle (default) 2 = bundle everything but the Python interpreter 1 = bundle everything, including the Python interpreter. A couple of years ago, when I was first learning py2exe, I asked on their mailing list what the best option was because I was having issues with bundle option 1. I was told that 3 was probably the most stable. I went with that and stopped having random problems, so that’s what I currently recommend. If you don’t like distributing more than one file, zip them up or create an installer. The only other option I use in this list is the dist_dir one. I use it to experiment with different built options or to create custom builds when I don’t want to overwrite my main good build. You can read about all the other options (including ones not even listed here) on the py2exe website. By setting optimize to 2, we can reduce the size of folder by about one megabyte.

Reducing a wxPython Script’s Binary Size

There’s a thread on the wxPython mailing list about reducing the size of the binary. I contacted Steven Sproat, one of the people in the thread, about what he did and here’s the trick: Set the “bundle_files” option to 1 and the the zipfile to None. The result will be three files in your dist folder: MSVCR71.dll, sampleApp.exe and w9xpopen.exe. The size of the folder is 5.94 MB on my machine (Tested on a Windows XP machine). As I mentioned earlier, setting bundle_files to 1 can cause some users to experience issues when running your application, but this may have been fixed with the newer versions of py2exe. Here’s what the setup.py file looks like with the new options set:

from distutils.core import setup
import py2exe

includes = []
excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
            'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
            'Tkconstants', 'Tkinter']
packages = []
dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
                'tk84.dll']

setup(
    options = {"py2exe": {"compressed": 2, 
                          "optimize": 2,
                          "includes": includes,
                          "excludes": excludes,
                          "packages": packages,
                          "dll_excludes": dll_excludes,
                          "bundle_files": 1,
                          "dist_dir": "dist",
                          "xref": False,
                          "skip_archive": False,
                          "ascii": False,
                          "custom_boot_script": '',
                         }
              },
    zipfile = None,
    windows=['sampleApp.py']
)

Another way to reduce the size of the folder is to use compression programs as one of my readers, ProgMan, pointed out. Here are his results:

Wrapping Up

You now know the basics for creating binaries with py2exe. I hope you have found this helpful for your current or future projects. If so, let me know in the comments!

Further Reading

3 thoughts on “A py2exe tutorial – Build a Binary Series!”

  1. Hi,

    I'll take a look at it too. From my brief skimming, Wix does sound interesting.

    – Mike

  2. Progman,

    That's cool. I know regular zipping programs can't compress those files that much. I've used izArc and filzip before and I don't remember the size difference being very substantial.

    – Mike

Comments are closed.