Как преподнести проект на Python рядовому пользователю без компиляторов и костылей, или подстраиваем велосипед под седока

в 6:43, , рубрики: python, usability, разработка под windows, юзабилити

Здравствуй, читатель.

Я люблю Python таким, какой он есть: шустрый, лаконичный, имеет из-под коробки очень много полезных вещей (например, можно узнать, является ли число полиморфом всего в 1 строку).
На нем в данный момент пишу клеточный автомат — упрощенное воссоздание жизни в чашке Пельтье. 5 классов живых существ, наследственность, влияние «силы водорода» на жизнь и развитие клетки. Но это уже другая история.

Собственно, это не осталось незамеченным со стороны окружающих. Появились люди, которые хотели «погонять» за свою колонию цианобактерий и вывести их в элиту своей чашки, вытеснив других. Но тут проявляется, по мнению некоторых, не очень хорошая особенность данного ЯП — он (почти) не компилируем — приходится с собой таскать 80 мегабайт дополнительного груза. А что если…

Цель — сделать Python более добрым по отношению к простому смертному пользователю, то есть:
— не принуждаем юзера ставить среду;
— не заставляем качать большие файлы (> 25 мб);
— не посылаем танцевать с бубнами, «нажми там, затем нарисуй пятиконечную звезду»;
— не теряем скорость выполнения приложения. Лишь чуток подождать в начале запуска.

Для своих целей я избрал вот такую алхимию:

1) Берем девственно чистый Python (в статье используется Python 3.4, но вас ничто не сдерживает использовать более поздниеранние версии), без Tkinter, IDLE, pydoc, тестов;

Спойлер

Как преподнести проект на Python рядовому пользователю без компиляторов и костылей, или подстраиваем велосипед под седока

2) Добавляем в него дополнительные (не стандартные, + нами специально изъятые) нужные модули, использующиеся в приложении, добавляем их в "/lib/" директорию. Помещаем в отдельную директорию папку с интерпретатором + исполняемый скрипт;
3) Сжимаем;

Осторожно, нецензурная лексика!

Как преподнести проект на Python рядовому пользователю без компиляторов и костылей, или подстраиваем велосипед под седока

4) Создаем на компилируемом ЯП (самое простое — pascal) маленькую программку, которая расшакалит распакует это дело уже на компьютере конечного пользователя и запустит;
5) Задача, выполнена, my master!

Пункты 1 и 2. Чистим Python

Этот пункт можно проделать и самостоятельно. Выполняем их для уменьшения размера готового велосипеда. (В развернутом состоянии интерпретатор у меня весит 60,9 мб, в сжатом — всего 11,9 мб, то есть сжатие составляет 80,5%, что немаловажно).

Осторожно, нецензурная лексика!

Как преподнести проект на Python рядовому пользователю без компиляторов и костылей, или подстраиваем велосипед под седока

Ссылка на Dropbox. (12 мб)

Затем складываем zip архив, файл (.py или .pyc) и сопутствующие файлы (картинки, ресурсы и проч.) в одну папку. Важно! Основной файл python должен называться main.py или main.pyc во избежание путаницы с другими .py или .pyc файлами вашего проекта.

Получится что-то похожее:

dir_test/
… python.zip
… main.py
… image.bmp
… README.txt

Пункт 3

Теперь сжимаем всё содержимое папки (!!!) в zip файл. Зачем мы снова архивируем интерпретатор? Интерпретатор мы сжимаем с высокой степенью сжатия и, если мы при каждой пробе будем заново архивировать его, то будет уходить довольно много времени.

Пункт 4

Теперь остается написать программу на pascal/delphi:

Код :

program python;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Zip, ShellAPI;

var a, path : string;
    zip : TZipFile;
    i, s, p : integer;
begin
  try
  // если уже проводилась распаковка...
    if fileexists('.sourse\main.py') or fileexists('.sourse\main.pyc') then begin
      writeln('Starting...');
      if fileexists('.sourse\main.py') then ShellExecute(0, nil, PChar('"'+ExtractFilePath(ParamStr(0))+'\.sourse\python\py.exe"'), PChar(string('"'+ExtractFilePath(ParamStr(0))+'\.sourse\main.py"')), '', 1)

      else if fileexists('.sourse\main.pyc') then ShellExecute(0, nil, PChar('"'+ExtractFilePath(ParamStr(0))+'\.sourse\python\py.exe"'), PChar(string('"'+ExtractFilePath(ParamStr(0))+'\.sourse\main.pyc"')), '', 1)
      else begin
        write('File not found! Press any key...');
        read(i);
      end;

      exit();
    end;

    //распаковываем zip-файл
    zip := TZipFile.Create;
    zip.UTF8Support := True;
    s := 0; // индикатор наличия main файла
    p := 0; //индикатор наличия python
    zip.Open('sourse.zip', zmRead);
    for I := 0 to zip.FileCount - 1 do
    begin
      writeln(zip.FileNames[i], ' extracting...');
      if zip.FileNames[i] = 'main.py' then s := 1;
      if zip.FileNames[i] = 'main.pyc' then s := 2;
      if zip.FileNames[i] = 'python.zip' then p := 1;
      zip.Extract(zip.FileNames[i], '.sourse');
    end;
    zip.Close;
    DeleteFile('sourse.zip');
    //начинаем работу с распаковкой пайтона
    write('Extracting Python, it may take a few seconds...');
    zip.Open('.sourse\python.zip', zmRead);
    zip.ExtractAll('.sourse\python');
    write('Done!'+#10#13 +'Starting...');
    zip.Destroy();
    DeleteFile('.sourse\python.zip');
    //проверки
    if s = 0 then      //если в ходе распаковки основной файл не был обнаружен
        begin
           write('No .py or .pyc file!', #10#13, 'Press any key...');
           read(a);
           exit();
        end;
    if p = 0 then      //если в ходе распаковки python не был обнаружен
        begin
           write('No Python!', #10#13, 'Press any key...');
           read(a);
           exit();
        end;
    //сам запуск
    if s = 1 then ShellExecute(0, nil, PChar('"'+ExtractFilePath(ParamStr(0))+'\.sourse\python\py.exe"'), PChar(string('"'+ExtractFilePath(ParamStr(0))+'\.sourse\main.py"')), '', 1)    //PChar(string(ExtractFilePath(ParamStr(0))+'\.sourse\main.py'))
    else ShellExecute(0, nil, PChar('"'+ExtractFilePath(ParamStr(0))+'\.sourse\python\py.exe"'), PChar(string('"'+ExtractFilePath(ParamStr(0))+'\.sourse\main.pyc"')), '', 1);    //PChar(string(ExtractFilePath(ParamStr(0))+'\.sourse\main.py'))

    exit();
  except
  //на случай, если что случится
    on E: Exception do begin
      Writeln(E.ClassName, ': ', E.Message);
      write('Broken! Press any button...');
      read(a);
    end;
  end;
end.

Программа весит 559 кб, что очень недурно.

Для проверки напишем скрипт-пробник на python:

Скрипт python

print('Привет! Я - переносной скрипт python и я занимаю в запакованном виде менее 20 мб!')
print('Сейчас я попытаюсь импортировать модуль random...')

try:
    import random
    print('Хех, мне это удалось!')
    print('Держи псевдослучайное число :', random.randrange(0,100))
except:
    print('%( Не получилось')

ragnarok = input('Нажмите любую кнопку...')

Всё это весит в запакованном виде 12 мегабайт (но по увеличению количества кода вес не будет существенно меняться) и работает быстро — по тестам первая распаковка длится не более 4 секунд, а последующие запуски — менее полсекунды.
В развернутом виде мы получаем 60,9 мегабайта.

Ссылка на тест:
https://www.dropbox.com/s/17xdpeju7u0oieh/python%20test.zip?dl=0 (Dropbox, 12 мб)

Подведем итог

Мы способны полноценно запускать python приложения на компьютерах под управлением Windows, где не стоит интерпретатор Python (также в ситуациях с установленной 2.x версией), увеличили мобильность приложения, не теряя скорости выполнения.

Спасибо, что дочитали мою статью. У меня мало опыта в таком деле, но я буду стараться! Следующей будет статья про клеточный автомат.

Автор: Koiki

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js