-1

I just wrote a python script and I would like to send it to a friend so they can use it. Rather than just sending them the whole text file and teaching them how to run it (and in turn giving them access to the source code), I was wondering how I can package it up as a "program" that they can just run when ever they want, without any access to the code itself.

Better yet, what should I be searching for? Instructions on how to do this would help but I would also like to know what exactly I can search to get more info about this.

Also, this will be from a unix machine to a unix machine.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Edany416
  • 49
  • 6
  • 1
    Unix comes with Python. If you don't have dependencies just make the .py file executable, add a hash bang and send it. – jonrsharpe Apr 04 '17 at 22:15
  • 1
    If you don't want to teach them how to run it, you can wait patiently for them to learn. – Josh Lee Apr 04 '17 at 22:19
  • @jonrsharpe Packaging is not usually as simple as that, unless you have zero dependencies and you know the target machine Python version is compatible. The better way is to write a `setup.py` file and create a distribution. – wim Apr 04 '17 at 22:26
  • Related, if not duplicate: http://stackoverflow.com/q/42728552/674039 – wim Apr 04 '17 at 22:27
  • @wim perhaps, more info required either way I think – jonrsharpe Apr 04 '17 at 22:27
  • Have you tried one of these? http://stackoverflow.com/questions/2933/how-can-i-create-a-directly-executable-cross-platform-gui-app-using-python –  Apr 04 '17 at 22:38

1 Answers1

1

Python supports executable archives. It is a way to wrap up a program and a bunch of modules into a single file. For example, youtube-dl is distributed like this.

Note that the "executable" is basically just a zip-file with a #!-line in front. So a knowledgable person can still get at the source code.

On UNIX it is easy to make this in a Makefile using zip. But on some platforms this is not as easy. So I built a Python-only solution called build.py.

"""Create runnable archives from program files and custom modules."""

import os
import py_compile
import tempfile
import zipfile as z


def mkarchive(name, modules, main='__main__.py',
              shebang=b'#!/usr/bin/env python3\n'):
    """Create a runnable archive.

    Arguments:
        name: Name of the archive.
        modules: Module name or iterable of module names to include.
        main: Name of the main file. Defaults to __main__.py
        shebang: Description of the interpreter to use. Defaults to Python 3.
    """
    std = '__main__.py'
    if isinstance(modules, str):
        modules = [modules]
    if main != std:
        try:
            os.remove(std)
        except OSError:
            pass
        os.link(main, std)
    # Forcibly compile __main__.py lest we use an old version!
    py_compile.compile(std)
    tmpf = tempfile.TemporaryFile()
    with z.PyZipFile(tmpf, mode='w', compression=z.ZIP_DEFLATED) as zf:
        zf.writepy(std)
        for m in modules:
            zf.writepy(m)
    if main != std:
        os.remove(std)
    tmpf.seek(0)
    archive_data = tmpf.read()
    tmpf.close()
    with open(name, 'wb') as archive:
        archive.write(shebang)
        archive.write(archive_data)
    os.chmod(name, 0o755)


if __name__ == '__main__':
    pass

This is not supposed to be used unmodified.

Copy build.py into your project. Then customize the part after in __name__ == '__main__', based on the example given below.

Suppose the directory src contains several Python files, eggs.py, ham.py and foo.py. It also contains a subdirectory spam, which contains a Python module that is used by all scripts. The following code will create three executable archives, eggs, ham and foo:

if __name__ == '__main__':
    from shutil import copy
    os.chdir('src')
    programs = [f for f in os.listdir('.') if f.endswith('.py')]
    for pyfile in programs:
        name = pyfile[:-3]
        mkarchive(name, 'spam', pyfile)
        copy(name, '../'+name)
        os.remove(name)

If you just want to use a single file:

if __name__ == '__main__':
    mkarchive('scriptname', None, 'filename.py')
Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • ok cool, thanks! Two questions about this. The whole script is just a single file called GradeCalculator.py. In this case do I need the line under if __name__ == '__main__': that starts with mkarchive, or can I omit that? (it looks like that line is related to the subdirectory spam. In my case I don't have any extra subdirectories). I probably should have mentioned the simplicity of my script before. Second question is, do I omit 'pass' (the very last line in the first block chunk of code)? – Edany416 Apr 04 '17 at 23:32
  • Ohh ok I see what is going on now. Thank you – Edany416 Apr 04 '17 at 23:44
  • It works. Had to make a couple edits to your code because I'm not using extra modules but other than that everything works. – Edany416 Apr 04 '17 at 23:53