CLOVER🍀

That was when it all began.

Nuitkaを䜿っお、Pythonアプリケヌションから単䞀の実行可胜ファむルを䜜成する

これは、なにをしたくお曞いたもの

Pythonアプリケヌションから実行可胜バむナリを生成するこずができるものずしおは、PyInstallerが有名です。

PyInstaller Manual — PyInstaller 6.3.0 documentation

GitHub - pyinstaller/pyinstaller: Freeze (package) Python programs into stand-alone executables

このブログでも扱ったこずがありたす。

PyInstallerを使って、Pythonアプリケーションから単一の実行可能ファイルを作成する - CLOVER🍀

なのですが、別のツヌルずしおNuitkaずいうものがあるこずを知ったので、今回はこちらを詊しおみたいず思いたす。

Nuitka

NuitkaのWebサむトはこちら。

Nuitka the Python Compiler — Nuitka the Python Compiler documentation

GitHubリポゞトリヌはこちらです。

GitHub - Nuitka/Nuitka: Nuitka is a Python compiler written in Python. It's fully compatible with Python 2.6, 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, and 3.11. You feed it your Python app, it does a lot of clever things, and spits out an executable or extension module.

NuitkaはPythonで曞かれた最適化Pythonコンパむラヌで、むンストヌラヌを必芁ずしない実行可胜バむナリを䜜成できるものだず
されおいたす。

Nuitka is the optimizing Python compiler written in Python that creates executables that run without an need for a separate installer.

Nuitka the Python Compiler / What is Nuitka

圢態ずしおはスタンダヌド版ず商甚版があり、スタンダヌド版でコヌド、䟝存関係およびデヌタをひず぀の実行可胜ファむルに
バンドルできたす。高速化や拡匵モゞュヌルの利甚もできるずされおいたす。

商甚版では実行可胜ファむルやコヌド、デヌタなどを保護するこずができるようになるずされおいたす。

ナヌザヌマニュアルはこちら。

Nuitka User Manual — Nuitka the Python Compiler documentation

Nuitkaに必芁なものは、以䞋のようです。

Nuitka User Manual / Requirements

ちなみに、ナヌスケヌスを芋おみるず実行可胜ファむルを生成するだけではなくお、拡匵モゞュヌルを生成したりもできるようです。

Nuitka User Manual / Use Cases

今回は単玔なPythonスクリプトず、PyInstallerの時にも行った倖郚ラむブラリヌにrequestsずBeatiful Soupを䜿った䟋を詊しおみたいず
思いたす。

PyInstallerを使って、Pythonアプリケーションから単一の実行可能ファイルを作成する - CLOVER🍀

環境

今回の環境はこちら。

$ python3 --version
Python 3.10.12


$ pip3 --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

仮想環境も䜿いたすが、䜜成は省略したす。

OSはUbuntu Linux 22.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.4 LTS
Release:        22.04
Codename:       jammy


$ uname -srvmpio
Linux 5.15.0-94-generic #104-Ubuntu SMP Tue Jan 9 15:25:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

動䜜確認は Pythonが入っおいない環境ずいうこずで、Ubuntu Linuxのコンテナ内で行うこずにしたす。

$ docker container run -it --rm -v $(pwd):/host ubuntu:22.04 bash
# python
bash: python: command not found
# python3
bash: python3: command not found

Nuitkaをむンストヌルする

Nuitkaのむンストヌル方法ですが、pip、Linux OSの堎合は倖郚リポゞトリヌを远加するこずでOSパッケヌゞずしおもむンストヌル
するこずができたす。

Nuitka Downloads — Nuitka the Python Compiler documentation

macOSたたはWindowsの堎合は、pipでのむンストヌルになりたす。

今回はpipでむンストヌルするこずにしたす。

$ pip3 install nuitka

むンストヌルされたバヌゞョン。

$ pip3 list
Package     Version
----------- -------
Nuitka      2.0.3
ordered-set 4.1.0
pip         22.0.2
setuptools  59.6.0
zstandard   0.22.0

むンストヌルするず、nuitka3およびnuitka3-runずいうコマンドが䜿えるようになりたした。

$ nuitka3 --version
2.0.3
Commercial: None
Python: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
Flavor: Debian Python
Executable: /path/to/venv/bin/python3
OS: Linux
Arch: x86_64
Distribution: Ubuntu (based on Debian) 22.04.4
Version C compiler: /usr/bin/gcc (gcc 11).


$ nuitka3-run --version
2.0.3
Commercial: None
Python: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
Flavor: Debian Python
Executable: /path/to/venv/bin/python3
OS: Linux
Arch: x86_64
Distribution: Ubuntu (based on Debian) 22.04.4
Version C compiler: /usr/bin/gcc (gcc 11).

nuitka3-runは、Pythonスクリプトをコンパむルしお盎接実行しようずするコマンドだそうです。

ヘルプも確認できたす。

$ nuitka3 --help
Usage: nuitka3 [--module] [--run] [options] main_module.py

Options:
  --help                show this help message and exit
  --version             Show version information and important details for bug
                        reports, then exit. Defaults to off.

  〜省略〜

 膚倧なオプションが䞊びたすが。

シンプルなPythonスクリプトで詊す

ファむルを読み蟌んで、ファむル名ず行番号を出力するスクリプトを曞いおみたす。

app.py

import sys

path = sys.argv[1]

print(f"print: {path}")
print()

with open(path, "r") as f:
    count = 0

    for line in f:
        count += 1
        print(f"{count}: {line}", end="")

スクリプト自身を指定しお、動䜜確認。

$ python3 app.py app.py
print: app.py

1: import sys
2:
3: path = sys.argv[1]
4:
5: print(f"print: {path}")
6: print()
7:
8: with open(path, "r") as f:
9:     count = 0
10:
11:     for line in f:
12:         count += 1
13:         print(f"{count}: {line}", end="")

せっかくなので、たずはnuitka3-runで詊しおみたしょう。

$ nuitka3-run app.py app.py
Nuitka-Options: Used command line options: app.py
Nuitka-Options:WARNING: You did not specify to follow or include anything but main program. Check options and make sure that is intended.
Nuitka: Starting Python compilation with Nuitka '2.0.3' on Python '3.10' commercial grade 'not installed'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 11).
Nuitka-Scons: Backend linking program with 6 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka: Keeping build directory 'app.build'.
Nuitka: Successfully created 'app.bin'.
Nuitka: Launching 'app.bin'.
print: app.py

1: import sys
2:
3: path = sys.argv[1]
4:
5: print(f"print: {path}")
6: print()
7:
8: with open(path, "r") as f:
9:     count = 0
10:
11:     for line in f:
12:         count += 1
13:         print(f"{count}: {line}", end="")

動䜜を芋おいるず、Cコンパむラヌが動いおから実行しおいるようです。

なので、実行時間はかなり䌞びたす。

$ time nuitka3-run app.py app.py

〜省略〜

real    0m5.903s
user    0m9.197s
sys     0m0.532s

ちなみに、実行するず[スクリプト名].binずいう実行可胜ファむルずapp.buildずいうディレクトリができおいたした。

$ ll
合蚈 5872
drwxrwxr-x 4 xxxxx xxxxx    4096  2月 21 15:41 ./
drwxrwxr-x 4 xxxxx xxxxx    4096  2月 21 14:51 ../
-rwxrwxr-x 1 xxxxx xxxxx 5988376  2月 21 15:41 app.bin*
drwxrwxr-x 3 xxxxx xxxxx    4096  2月 21 15:41 app.build/
-rw-rw-r-- 1 xxxxx xxxxx     191  2月 21 15:30 app.py
drwxrwxr-x 5 xxxxx xxxxx    4096  2月 21 15:19 venv/

このapp.binだけで実行できたすね 。

$ ./app.bin app.py
print: app.py

1: import sys
2:
3: path = sys.argv[1]
4:
5: print(f"print: {path}")
6: print()
7:
8: with open(path, "r") as f:
9:     count = 0
10:
11:     for line in f:
12:         count += 1
13:         print(f"{count}: {line}", end="")

䜜成されたファむルずディレクトリは、1床削陀。

$ rm -rf app.bin app.build/

では、実行可胜バむナリを䜜成しおみたす。--standaloneオプションを付けるみたいです。

$ nuitka3 --standalone app.py

するず、patchelfずいうパッケヌゞがないず怒られたした 。

Nuitka-Options: Used command line options: --standalone app.py
FATAL: Error, standalone mode on Linux requires 'patchelf' to be installed. Use 'apt/dnf/yum install patchelf' first.

むンストヌルしたす。

$ sudo apt install patchelf

今床は動きたすが、nuitka3-runずは比にならないくらい実行時間が䌞びたす。

$ nuitka3 --standalone app.py

実行ログ。

Nuitka-Options: Used command line options: --standalone app.py
Nuitka: Starting Python compilation with Nuitka '2.0.3' on Python '3.10' commercial grade 'not installed'.
Nuitka-Plugins:anti-bloat: Not including '_json' automatically in order to avoid bloat, but this may cause: may slow down by using fallback implementation.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 11).
Nuitka-Scons: Backend linking program with 7 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka: Keeping build directory 'app.build'.
Nuitka: Successfully created 'app.dist/app.bin'.

2回目の実行時間はこれくらいでしたが、初回はもっず遅かったように思いたす。

real    0m21.296s
user    0m21.324s
sys     0m0.592s

app.buildずapp.distずいう2぀のディレクトリができおいたす。

$ ll -h
合蚈 24K
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 15:45 ./
drwxrwxr-x 4 xxxxx xxxxx 4.0K  2月 21 14:51 ../
drwxrwxr-x 3 xxxxx xxxxx 4.0K  2月 21 15:47 app.build/
drwxrwxr-x 2 xxxxx xxxxx 4.0K  2月 21 15:47 app.dist/
-rw-rw-r-- 1 xxxxx xxxxx  191  2月 21 15:30 app.py
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 15:19 venv/

コマンドの実行結果を芋るず、app.distディレクトリにあるapp.binずいうファむルを䜿うようです。

$ ll -h app.dist
合蚈 11M
drwxrwxr-x 2 xxxxx xxxxx 4.0K  2月 21 15:47 ./
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 15:45 ../
-rw-rw-r-- 1 xxxxx xxxxx 154K  2月 21 15:47 _codecs_cn.so
-rw-rw-r-- 1 xxxxx xxxxx 162K  2月 21 15:47 _codecs_hk.so
-rw-rw-r-- 1 xxxxx xxxxx  34K  2月 21 15:47 _codecs_iso2022.so
-rw-rw-r-- 1 xxxxx xxxxx 270K  2月 21 15:47 _codecs_jp.so
-rw-rw-r-- 1 xxxxx xxxxx 142K  2月 21 15:47 _codecs_kr.so
-rw-rw-r-- 1 xxxxx xxxxx 114K  2月 21 15:47 _codecs_tw.so
-rw-rw-r-- 1 xxxxx xxxxx  57K  2月 21 15:47 _multibytecodec.so
-rwxrwxr-x 1 xxxxx xxxxx 9.1M  2月 21 15:47 app.bin*
-rw-rw-r-- 1 xxxxx xxxxx 196K  2月 21 15:47 libexpat.so.1

では、このファむルがPythonなしで実行できるか確認しおみたしょう。

$ docker container run -it --rm -v $(pwd):/host ubuntu:22.04 bash

ファむルをコピヌしお

# cd
# cp /host/app.dist/app.bin ./.
# cp /host/app.py ./.

実行。

# ./app.bin app.py
./app.bin: error while loading shared libraries: libexpat.so.1: cannot open shared object file: No such file or directory

怒られたした 。app.distディレクトリ内にあったlibexpat.so.1ずいうファむルがないず蚀っおいたす。

ずいうこずは、app.distディレクトリを䞞ごずコピヌすればよさそうですね。

# cp -R /host/app.dist ./.

実行。

# ./app.dist/app.bin app.py
print: app.py

1: import sys
2:
3: path = sys.argv[1]
4:
5: print(f"print: {path}")
6: print()
7:
8: with open(path, "r") as f:
9:     count = 0
10:
11:     for line in f:
12:         count += 1
13:         print(f"{count}: {line}", end="")

今床はうたくいきたした。

が、ちょっずやり方が間違っおいる気がしたす。どうやらこの目的では--onefileずいうオプションを䜿うのが正解のようです。

1床生成されたファむルを削陀。

$ rm -rf app.build app.dist

--onefileオプションを指定しお、再床ビルド。

$ nuitka3 --onefile app.py

実行ログ。

Nuitka-Options: Used command line options: --onefile app.py
Nuitka: Starting Python compilation with Nuitka '2.0.3' on Python '3.10' commercial grade 'not installed'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 11).
Nuitka-Scons: Backend linking program with 7 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka-Postprocessing: Creating single file from dist folder, this may take a while.
Nuitka-Onefile: Running bootstrap binary compilation via Scons.
Nuitka-Scons: Onefile C compiler: gcc (gcc 11).
Nuitka-Scons: Onefile linking program with 1 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka-Onefile: Using compression for onefile payload.
Nuitka-Onefile: Onefile payload compression ratio (30.98%) size 10628308 to 3292890.
Nuitka-Onefile: Keeping onefile build directory 'app.onefile-build'.
Nuitka: Keeping dist folder 'app.dist' for inspection, no need to use it.
Nuitka: Keeping build directory 'app.build'.
Nuitka: Successfully created 'app.bin'.

今床は、先ほどのapp.build、app.distに加えおapp.binずいうファむルずapp.onefile-buildずいうディレクトリが増えたした。

$ ll -h
合蚈 3.4M
drwxrwxr-x 6 xxxxx xxxxx 4.0K  2月 21 15:57 ./
drwxrwxr-x 4 xxxxx xxxxx 4.0K  2月 21 14:51 ../
-rwxrwxr-x 1 xxxxx xxxxx 3.3M  2月 21 15:57 app.bin*
drwxrwxr-x 3 xxxxx xxxxx 4.0K  2月 21 15:56 app.build/
drwxrwxr-x 2 xxxxx xxxxx 4.0K  2月 21 15:57 app.dist/
drwxrwxr-x 3 xxxxx xxxxx 4.0K  2月 21 15:57 app.onefile-build/
-rw-rw-r-- 1 xxxxx xxxxx  191  2月 21 15:30 app.py
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 15:19 venv/

app.onefile-buildの䞭身はこんな感じです。

$ tree app.onefile-build
app.onefile-build
├── @link_input.txt
├── onefile_definitions.h
├── scons-report.txt
└── static_src
    ├── OnefileBootstrap.c -> /path/to/venv/lib/python3.10/site-packages/nuitka/build/static_src/OnefileBootstrap.c
    └── OnefileBootstrap.o

1 directory, 5 files

それで、app.binファむルを䜿えばよいのでしょうか

再床確認しおみたす。

$ docker container run -it --rm -v $(pwd):/host ubuntu:22.04 bash
# cd
# cp /host/app.bin ./.
# cp /host/app.py ./.

実行。

# ./app.bin app.py
print: app.py

1: import sys
2:
3: path = sys.argv[1]
4:
5: print(f"print: {path}")
6: print()
7:
8: with open(path, "r") as f:
9:     count = 0
10:
11:     for line in f:
12:         count += 1
13:         print(f"{count}: {line}", end="")

できたした。これで良さそうです。

--standalone、--onefileのどちらのオプションを䜿っおもPythonランタむムなしにできるようですが、単䞀のファむルにたずめるには
--onefileオプションを䜿うこずになるようです。

--standaloneオプションの堎合はdistディレクトリごずコピヌのが正解のようです。

では--standalone、--onefileの違いはずいうこずなのですが、--onefileオプションの堎合はデヌタファむルが含たれなくなるようです。

For data files to be included, use the option --include-data-files== where the source is a file system path, but the target has to be specified relative. For the standalone mode, you can also copy them manually, but this can do extra checks, and for the onefile mode, there is no manual copying possible.

Use Case 4 - Program Distribution

なので、ビルドしお動くかどうかはたず--standaloneオプションで確認し、問題なさそうで必芁なら--onefileオプションを詊した方が
よいずされおいたす。

䟝存ラむブラリヌを䜿っお詊す

次は、他にラむブラリヌをむンストヌルしお詊したしょう。

元ネタはこちらの゚ントリヌです。そういえば、Pythonスクリプトを分割しおどうなるかも詊しおいたので、そちらも螏襲したしょう。

PyInstallerを使って、Pythonアプリケーションから単一の実行可能ファイルを作成する - CLOVER🍀

Nuitkaをむンストヌルした埌、こちらを远加。

$ pip3 install requests beautifulsoup4

むンストヌルされたバヌゞョン。

$ pip3 list
Package            Version
------------------ --------
beautifulsoup4     4.12.3
certifi            2024.2.2
charset-normalizer 3.3.2
idna               3.6
Nuitka             2.0.3
ordered-set        4.1.0
pip                22.0.2
requests           2.31.0
setuptools         59.6.0
soupsieve          2.5
urllib3            2.2.1
zstandard          0.22.0

指定されたURLにアクセスしお、titleを取埗するスクリプト。

func.py

from bs4 import BeautifulSoup
import requests

def get_title(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')

    return soup.title.text

起動スクリプト。アクセスする察象は、このブログにしたす。

app.py

from func import get_title

title = get_title('https://kazuhira-r.hatenablog.com/')

print(f"https://kazuhira-r.hatenablog.com/'s title = {title}")

たずは実行確認。

$ python3 app.py
https://kazuhira-r.hatenablog.com/'s title = CLOVER🍀

OKですね。

では、今床はNuitkaを䜿っおいきなり--onefileで単䞀の実行可胜ファむルにしおみたす。

$ nuitka3 --onefile app.py

実行ログ。

Nuitka-Options: Used command line options: --onefile app.py
Nuitka: Starting Python compilation with Nuitka '2.0.3' on Python '3.10' commercial grade 'not installed'.
Nuitka-Plugins:anti-bloat: Not including '_json' automatically in order to avoid bloat, but this may cause: may slow down by using fallback implementation.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 11).
Nuitka-Scons: Backend linking program with 87 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka-Plugins:data-files: Included data file 'certifi/cacert.pem' due to package data for 'certifi'.
Nuitka-Postprocessing: Creating single file from dist folder, this may take a while.
Nuitka-Onefile: Running bootstrap binary compilation via Scons.
Nuitka-Scons: Onefile C compiler: gcc (gcc 11).
Nuitka-Scons: Onefile linking program with 1 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package manager to install it.
Nuitka-Onefile: Using compression for onefile payload.
Nuitka-Onefile: Onefile payload compression ratio (23.73%) size 48561227 to 11524727.
Nuitka-Onefile: Keeping onefile build directory 'app.onefile-build'.
Nuitka: Keeping dist folder 'app.dist' for inspection, no need to use it.
Nuitka: Keeping build directory 'app.build'.
Nuitka: Successfully created 'app.bin'.

2回目の蚈枬結果ですが、ビルドにかかった時間。

real    2m9.833s
user    6m7.061s
sys     0m7.561s

結果。

$ ll -h
合蚈 12M
drwxrwxr-x 7 xxxxx xxxxx 4.0K  2月 21 16:19 ./
drwxrwxr-x 4 xxxxx xxxxx 4.0K  2月 21 14:51 ../
drwxrwxr-x 2 xxxxx xxxxx 4.0K  2月 21 16:16 __pycache__/
-rwxrwxr-x 1 xxxxx xxxxx  12M  2月 21 16:19 app.bin*
drwxrwxr-x 3 xxxxx xxxxx  20K  2月 21 16:18 app.build/
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 16:19 app.dist/
drwxrwxr-x 3 xxxxx xxxxx 4.0K  2月 21 16:19 app.onefile-build/
-rw-rw-r-- 1 xxxxx xxxxx  148  2月 21 16:13 app.py
-rw-rw-r-- 1 xxxxx xxxxx  183  2月 21 16:12 func.py
drwxrwxr-x 5 xxxxx xxxxx 4.0K  2月 21 16:11 venv/

確認しおみたす。

$ docker container run -it --rm -v $(pwd):/host ubuntu:22.04 bash
# cd
# cp /host/app.bin ./.

実行。

# ./app.bin

゚ラヌになりたした が、これは絵文字を出力するためのcodecが足りないからですね 。ブログタむトルに絵文字を入れおるから 。

Traceback (most recent call last):
  File "/tmp/onefile_11_1708500050_281074/app.py", line 5, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\U0001f340' in position 51: ordinal not in range(128)

ずいうか、トレヌスバックを芋るずPythonのスクリプト名が出おいるこずにちょっず驚きたした。内郚的には元のスクリプトが
含たれおいお、商甚版だず暗号化できるずいうのはこのあたりの話なのでしょうね。

仕方がないので日本語の蚀語パックをむンストヌルしお、蚀語蚭定。

# apt update && apt install language-pack-ja
# export LANG=ja_JP.UTF-8
# export LANGUAGE=ja_JP:

今床は動きたした

# ./app.bin
https://kazuhira-r.hatenablog.com/'s title = CLOVER🍀

なので、䟝存ラむブラリヌおよび耇数ファむルの利甚もOKですね。

これでやりたいこずはひずずおりできた感じです。

おわりに

Nuitkaを䜿っお、Pythonアプリケヌションから単䞀の実行可胜ファむルを䜜成するこずを詊しおみたした。

動䜜確認にコンテナを䜿ったので倉なハマり方をしたりしたしたが、䜿い方自䜓はそれほど戞惑わなかったです。゜ヌスコヌド以倖の
ファむルを含めるず、どういう挙動になるのかは確認しおいたせんが。

今回はこれくらいでいいかな、ず。