Nuitkaを使ってスクリプトをバイナリ化してみよう
https://gyazo.com/ccf580592784ab86ed44539cdbabea37
はじめに
Python は、作成したスクリプトをバイナリ化して配布したいといった機能がなりません。いくつかバイナリ化の機能を提供するツールもあったのですが、プラットフォームが限定されていたり、作成されるバイナリが大きすぎたりと、なかなかうまい解決策が見つけられませんでした。そうしたなか、にnuitka が登場したわけです。
この資料は、nuitka についてまとめたものです。
Nuitka について
NuitkaはPythonで実装されたコンパイラです。Nuitka は Pythonインタープリタをそのまま代替することができ、加えていくつかの拡張がされています。CPython 2.6, 2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 が持つすべてのコンストラクトを、そのPythonバージョンで実行されるときにコンパイルします。
そして、コンパイルされていないコードとコンパイルされたコードを非常に互換性のある方法で一緒に実行します。
すべてのPythonライブラリモジュールとすべての拡張モジュールを自由に使用することができます。
NuitkaはPythonモジュールをCレベルのプログラムに変換し、libpythonと独自の静的Cファイルを使用してCPythonと同じように実行します。
すべての最適化は不必要なオーバーヘッドを避けることを目的としていて、標準的なPythonのすべてのバグがエミュレートされるわけではありません。例えば、Nuitka ではより完全なエラーメッセージが表示されますが、それを無効にする完全互換モードもあります。
コンパイル・バイナリの他のマシンでの実行
Nuitka で作成されたバイナリは、--standalone や --onefile オプションを使って、Python のインストールとは独立して実行することができます。
バイナリファイルのサフィックス
Nitka で作成されたバイナリは、Windowsでは .exe というサフィックス(拡張子)を持ちます。他のプラットフォームでは、スタンドアローンモード用のサフィックスや、.bin といったサフィックスはOSレベルでは必要ありませんが、-e オプションで任意のサフィックスを付加することができます。
アクセラレーションモード用のサフィックスは、オリジナルのスクリプト名とバイナリ名が衝突しないようにするために追加されます。
CPython、Anaconda Pythonである必要がある
Nuitkaを実行するには、Pythonの標準的な実装である「CPython」が必要です。これはPythonの実装内容と密接に関係しているからです。Windowsでは、システムワイドにインストールされていないPythonやアクセラレーションモードのPythonについては、PythonXX.DLLを一緒にコピーする必要があります。これはNuitka が自動的に行います。
WindowsアプリストアからのPythonでないこと
WindowsアプリストアのPythonはうまく動作しないことが知られています。Nuitka では、実行時にプラットフォームのPython をチェックしています。また、macOS では "pyenv "がうまく動作しない可能性が高いことに注意してください。
オペレーティングシステム
サポートしているOSは、Linux、FreeBSD、NetBSD、macOS X、Windows(32/64ビット)です。
その他のOSでも動作するかもしれません。移植性はおおむね良好だと思われますが、Scionの使い方などには工夫が必要かもしれません。WindowsのPythonとCコンパイラのアーキテクチャに合わせるようにしてください。
アーキテクチャー
動作確認されているアーキテクチャは x86, x86_64 (amd64), arm, その他多数あります。
Nuitka は一般的にハードウェアの機能を使用しないため、他のアーキテクチャもすぐに動作することが期待できます。上記のアーキテクチャは、テストされて、結果が良好であることがわかっているものだけです。一般的に、Debian がサポートしているアーキテクチャも、テスト済みで良い結果なものと考えられます。
インストール
Nuitka にインストールはいくつか方法がありますが、ここでは pip でインストールする方法を例示します。
code: bash
$ pip install nuitka
プラットフォームに依存したインストーラやパッケージは次のURLからダウンロードすることができます。
コマンドライン
nuitka をインストールすると、pip がインストールされている Python のバイナリパスに nuitka3コマンドが配置されます。
あるいは、次のように Python モジュールとして実行してもかまいません。
code: bash
$ python -m nuitka
--helpオプションを与えると簡単な説明(それでもかなりの量)が出力されます。
code: bash
% nuitka3 --help
nuitka3: error: no such option: --hepl
(py39) goichiiisaka@GoichiMacBook answer % nuitka3 --help
Options:
--version show program's version number and exit
-h, --help show this help message and exit
--module Create an extension module executable instead of a
program. Defaults to off.
--standalone Enable standalone mode for output. This allows you to
transfer the created binary to other machines without
it using an existing Python installation. This also
means it will become big. It implies these option: "--
follow-imports". You may also want to use "--python-
flag=no_site" to avoid the "site.py" module, which can
save a lot of code dependencies. Defaults to off.
--onefile On top of standalone mode, enable onefile mode. This
means not a folder, but a compressed executable is
created and used. Defaults to off.
--python-debug Use debug version or not. Default uses what you are
using to run Nuitka, most likely a non-debug version.
--python-flag=PYTHON_FLAGS
Python flags to use. Default uses what you are using
to run Nuitka, this enforces a specific mode. These
are options that also exist to standard Python
executable. Currently supported: "-S" (alias
"nosite"), "static_hashes" (do not use hash
randomization), "no_warnings" (do not give Python
runtime warnings), "-O" (alias "noasserts"). Default
empty.
--python-for-scons=PYTHON_SCONS
If using Python3.3 or Python3.4, provide the path of a
Python binary to use for Scons. Otherwise Nuitka can
use what you run Nuitka with or a "scons" binary that
is found in PATH, or a Python installation from
Windows registry.
--warn-implicit-exceptions
Enable warnings for implicit exceptions detected at
compile time.
--warn-unusual-code Enable warnings for unusual code detected at compile
time.
--assume-yes-for-downloads
Allow Nuitka to download code if necessary, e.g.
dependency walker on Windows.
Control the inclusion of modules and packages in result.:
--include-package=PACKAGE
Include a whole package. Give as a Python namespace,
e.g. some_package.sub_package and Nuitka will then
find it and include it and all the modules found below
that disk location in the binary or extension module
it creates, and make it available for import by the
code. Default empty.
--include-module=MODULE
Include a single module. Give as a Python namespace,
e.g. some_package.some_module and Nuitka will then
find it and include it in the binary or extension
module it creates, and make it available for import by
the code. Default empty.
--include-plugin-directory=MODULE/PACKAGE
Include the content of that directory, no matter if
it's used by the given main program in a visible form.
Overrides all other recursion options. Can be given
multiple times. Default empty.
--include-plugin-files=PATTERN
Include into files matching the PATTERN. Overrides all
other follow options. Can be given multiple times.
Default empty.
--prefer-source-code
For already compiled extension modules, where there is
both a source file and an extension module, normally
the extension module is used, but it should be better
to compile the module from available source code for
best performance. If not desired, there is --no-
prefer-source-code to disable warnings about it.
Default off.
Control the following into imported modules:
--follow-stdlib Also descend into imported modules from standard
library. This will increase the compilation time by a
lot. Defaults to off.
--nofollow-imports When --nofollow-imports is used, do not descend into
any imported modules at all, overrides all other
recursion options. Defaults to off.
--follow-imports When --follow-imports is used, attempt to descend into
all imported modules. Defaults to off.
--follow-import-to=MODULE/PACKAGE
Follow to that module if used, or if a package, to the
whole package. Can be given multiple times. Default
empty.
--nofollow-import-to=MODULE/PACKAGE
Do not follow to that module name even if used, or if
a package name, to the whole package in any case,
overrides all other options. Can be given multiple
times. Default empty.
Data files for standalone/onefile mode:
--include-package-data=PACKAGE_DATA
Include data files of the given package name. Can use
patterns. By default Nuitka does not unless hard coded
and vital for operation of a package. This will
include all non-DLL, non-extension modules in the
distribution. Default empty.
--include-data-file=DATA_FILES
Include data files by filenames in the distribution.
There are many allowed forms. With '--include-data-
file=/path/to/file/*.txt=folder_name/some.txt' it will
copy a single file and complain if it's multiple. With
'--include-data-
file=/path/to/files/*.txt=folder_name/' it will put
all matching files into that folder. For recursive
copy there is a form with 3 values that '--include-
data-file=/path/to/scan=folder_name=**/*.txt' that
will preserve directory structure. Default empty.
--include-data-dir=DATA_DIRS
Include data files from complete directory in the
distribution. This is recursive. Check '--include-
data-file' with patterns if you want non-recursive
inclusion. An example would be '--include-data-
dir=/path/somedir=data/somedir' for plain copy, of the
whole directory. All files are copied, if you want to
exclude files you need to remove them beforehand.
Default empty.
Immediate execution after compilation:
--run Execute immediately the created binary (or import the
compiled module). Defaults to off.
--debugger, --gdb Execute inside a debugger, e.g. "gdb" or "lldb" to
automatically get a stack trace. Defaults to off.
--execute-with-pythonpath
When immediately executing the created binary
(--execute), don't reset PYTHONPATH. When all modules
are successfully included, you ought to not need
PYTHONPATH anymore.
Dump options for internal tree:
--xml Dump the final result of optimization as XML, then
exit.
Code generation choices:
--full-compat Enforce absolute compatibility with CPython. Do not
even allow minor deviations from CPython behavior,
e.g. not having better tracebacks or exception
messages which are not really incompatible, but only
different. This is intended for tests only and should
not be used for normal use.
--file-reference-choice=FILE_REFERENCE_MODE
Select what value "__file__" is going to be. With
"runtime" (default for standalone binary mode and
module mode), the created binaries and modules, use
the location of themselves to deduct the value of
"__file__". Included packages pretend to be in
directories below that location. This allows you to
include data files in deployments. If you merely seek
acceleration, it's better for you to use the
"original" value, where the source files location will
be used. With "frozen" a notation "<frozen
module_name>" is used. For compatibility reasons, the
"__file__" value will always have ".py" suffix
independent of what it really is.
Output choices:
-o FILENAME Specify how the executable should be named. For
extension modules there is no choice, also not for
standalone mode and using it will be an error. This
may include path information that needs to exist
though. Defaults to '<program_name>' on this platform.
.bin
--output-dir=DIRECTORY
Specify where intermediate and final output files
should be put. The DIRECTORY will be populated with C
files, object files, etc. Defaults to current
directory.
--remove-output Removes the build directory after producing the module
or exe file. Defaults to off.
--no-pyi-file Do not create a ".pyi" file for extension modules
created by Nuitka. This is used to detect implicit
imports. Defaults to off.
Debug features:
--debug Executing all self checks possible to find errors in
Nuitka, do not use for production. Defaults to off.
--unstripped Keep debug info in the resulting object file for
better debugger interaction. Defaults to off.
--profile Enable vmprof based profiling of time spent. Not
working currently. Defaults to off.
--graph Create graph of optimization process. Defaults to off.
--trace-execution Traced execution output, output the line of code
before executing it. Defaults to off.
--recompile-c-only This is not incremental compilation, but for Nuitka
development only. Takes existing files and simply
compile them as C again. Allows compiling edited C
files for quick debugging changes to the generated
source, e.g. to see if code is passed by, values
output, etc, Defaults to off. Depends on compiling
Python source to determine which files it should look
at.
--generate-c-only Generate only C source code, and do not compile it to
binary or module. This is for debugging and code
coverage analysis that doesn't waste CPU. Defaults to
off. Do not think you can use this directly.
--experimental=EXPERIMENTAL
Use features declared as 'experimental'. May have no
effect if no experimental features are present in the
code. Uses secret tags (check source) per experimented
feature.
Backend C compiler choice:
--clang Enforce the use of clang. On Windows this requires a
working Visual Studio version to piggy back on.
Defaults to off.
-j N, --jobs=N Specify the allowed number of parallel C compiler
jobs. Defaults to the system CPU count.
--lto Use link time optimizations if available and usable
(MSVC, gcc >=4.6, clang). Defaults to off.
--static-libpython=STATIC_LIBPYTHON
Use static link library of Python if available.
Defaults to auto, i.e. enabled for where we know it's
working.
Tracing features:
--quiet Disable all information outputs, but show warnings.
Defaults to off.
--show-scons Operate Scons in non-quiet mode, showing the executed
commands. Defaults to off.
--show-progress Provide progress information and statistics. Defaults
to off.
--no-progress Disable progress bar outputs (if tqdm is installed).
Defaults to off.
--show-memory Provide memory information and statistics. Defaults to
off.
--show-modules Provide information for included modules and DLLs
Defaults to off.
--show-modules-output=SHOW_INCLUSION_OUTPUT
Where to output --show-modules, should be a filename.
Default is standard output.
--verbose Output details of actions taken, esp. in
optimizations. Can become a lot. Defaults to off.
--verbose-output=VERBOSE_OUTPUT
Where to output --verbose, should be a filename.
Default is standard output.
Windows specific controls:
--windows-disable-console
When compiling for Windows, disable the console
window. Defaults to off.
--windows-icon-from-ico=ICON_PATH
Add executable icon. Can be given multiple times for
different resolutions or files with multiple icons
inside. In the later case, you may also suffix with
#<n> where n is an integer index starting from 1, specifying a specific icon to be included, and all
others to be ignored.
--windows-icon-from-exe=ICON_EXE_PATH
Copy executable icons from this existing executable
(Windows only).
--windows-uac-admin
Request Windows User Control, to grant admin rights on
execution. (Windows only). Defaults to off.
--windows-uac-uiaccess
Request Windows User Control, to enforce running from
a few folders only, remote desktop access. (Windows
only). Defaults to off.
--windows-company-name=WINDOWS_COMPANY_NAME
Name of the company to use in Windows Version
information. One of file or product version is
required, when a version resource needs to be added,
e.g. to specify product name, or company name.
Defaults to unused.
--windows-product-name=WINDOWS_PRODUCT_NAME
Name of the product to use in Windows Version
information. Defaults to base filename of the binary.
--windows-file-version=WINDOWS_FILE_VERSION
File version to use in Windows Version information.
Must be a sequence of up to 4 numbers, nothing else
allowed. One of file or product version is required,
when a version resource needs to be added, e.g. to
specify product name, or company name. Defaults to
unused.
--windows-product-version=WINDOWS_PRODUCT_VERSION
Product version to use in Windows Version information.
Must be a sequence of up to 4 numbers, nothing else
allowed. One of file or product version is required,
when a version resource needs to be added, e.g. to
specify product name, or company name. Defaults to
unused.
--windows-file-description=WINDOWS_FILE_DESCRIPTION
Description of the file use in Windows Version
information. One of file or product version is
required, when a version resource needs to be added,
e.g. to specify product name, or company name.
Defaults to nonsense.
--windows-onefile-tempdir-spec=ONEFILE_TEMPDIR_SPEC
Use this as a temporary folder. Defaults to
'%TEMP%\onefile_%PID%_%TIME%', i.e. system temporary
directory.
--windows-force-stdout-spec=WINDOWS_FORCE_STDOUT_SPEC
Force standard output of the program to go to this
location. Useful for programs with disabled console
and programs using the Windows Services Plugin of
Nuitka. Defaults to not active, use e.g.
'%PROGRAM%.out.txt', i.e. file near your program.
--windows-force-stderr-spec=WINDOWS_FORCE_STDERR_SPEC
Force standard error of the program to go to this
location. Useful for programs with disabled console
and programs using the Windows Services Plugin of
Nuitka. Defaults to not active, use e.g.
'%PROGRAM%.err.txt', i.e. file near your program.
Linux specific controls:
--linux-onefile-icon=ICON_PATH
Add executable icon for onefile binary to use. Can be
given only one time. Defaults to Python icon if
available.
Plugin control:
--plugin-enable=PLUGINS_ENABLED, --enable-plugin=PLUGINS_ENABLED
Enabled plugins. Must be plug-in names. Use --plugin-
list to query the full list and exit. Default empty.
--plugin-disable=PLUGINS_DISABLED, --disable-plugin=PLUGINS_DISABLED
Disabled plugins. Must be plug-in names. Use --plugin-
list to query the full list and exit. Default empty.
--plugin-no-detection
Plugins can detect if they might be used, and the you
can disable the warning via --plugin-disable=plugin-
that-warned, or you can use this option to disable the
mechanism entirely, which also speeds up compilation
slightly of course as this detection code is run in
vain once you are certain of which plug-ins to use.
Defaults to off.
--plugin-list Show list of all available plugins and exit. Defaults
to off.
--user-plugin=USER_PLUGINS
The file name of user plugin. Can be given multiple
times. Default empty.
もうひとつ nuitka-run コマンドも使えるようになります。これは、 nuitka と同じですが、Pythonスクリプトをコンパイルして直接実行しようとします。
これも、--helpオプシンで受け付けます。
コンパイル
次のようなコードがあるとします。
code: helloworld.py
def talk(message):
return "Talk " + message
def main():
print(talk("Hello World"))
if __name__ == "__main__":
main()
まず、Python でこのスクリプトを実行して動作することを確認しましょう。
code: bash
% python helloworld.py
Talk Hello World
次に、nuitka でこのコードをコンパイルしてみましょう。
code: bash
% python -m nuitka helloworld.py
Nuitka-Options:INFO: Used command line options: helloworld.py
Nuitka:INFO: Starting Python compilation with Nuitka '0.6.16.4' on Python '3.9' commercial None.
Nuitka:INFO: Completed Python level compilation and optimization.
Nuitka:INFO: Generating source code for C backend compiler.
Nuitka:INFO: Running data composer tool for optimal constant value handling.
Nuitka:INFO: Running C level backend compilation via Scons.
Nuitka-Scons:INFO: Backend C compiler: clang (clang).
Nuitka will make use of ccache to speed up repeated compilation.
Is it OK to download and put it in '/Users/goichiiisaka/Library/Application Support/Nuitka/ccache/v4.2.1'.
No installer needed, cached, one time question.
Proceed and download? Yes/No Nuitka:INFO: Extracting to '/Users/goichiiisaka/Library/Application Support/Nuitka/ccache/v4.2.1/ccache'
Nuitka-Scons:INFO: Compiled 12 C files using ccache.
Nuitka-Scons:INFO: Cached C files (using ccache) with result 'disabled': 1
Nuitka-Scons:INFO: Cached C files (using ccache) with result 'cache miss': 11
Nuitka:INFO: Keeping build directory 'helloworld.build'.
Nuitka:INFO: Successfully created 'helloworld.bin'.
%
プラットフォームでccache がインストールされていなければ、ダウンロードするかと質問があります。
コンパイルが終わると、binとbuildの拡張子がついたファイルが作成されます。
Windows では拡張子が .exe となります。
code: bash
% ls
helloworld.bin helloworld.build helloworld.py
bin拡張子のファイルはコンパイル済みのバイナリで、直接実行することができます。
コマンド検索のための環境変数 PATH を カレントディレクトリだけに置き換えて実行できています。
つまり、単独で実行することができるわけで、実行するために Python インタプリタは不要です。
code: bash
% file ./helloworld.bin
./helloworld.bin: Mach-O 64-bit executable x86_64
% ./helloworld.bin
Talk Hello World
% env PATH=. helloworld.bin
Talk Hello World
少し考えるとわかると思いますが、実行には Python インタプリタは使用しませんが、スクリプト側で使用する(インポートする)Python ライブラリや、使用しているプラットフォームにインストールしているCライブラリは実行時に必要になります。
また、アーキテクチャの異なるマシンでは実行できません、
code: bash
$ uname -a
Linux dev01 3.10.0-1160.31.1.el7.x86_64 #1 SMP Thu Jun 10 13:32:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ file ./helloworld.bin
./helloworld.bin: Mach-O 64-bit executable
$ chmod 700 ./helloworld.bin
$ ./helloworld.bin
-bash: ./helloworld.bin: cannot execute binary file
コンパイル済みのバイナリを配布
コンパイル済みのバイナリを配布するには、--standalone オプションを付けてビルドします。これで、単一の実行ファイルではなく、フォルダに出力されます。hello.dist フォルダを相手のマシンにコピーして実行してください。
また、--onefile オプションを与えると、単一のファイルを作成します。ただし、データファイルがない場合などにデバッグが困難になるため、--onefile を使用する前には、--standalone で期待どおりに動作することを確認するようにしてください。
code: bash
% ls
helloworld.bin helloworld.dist
helloworld.build helloworld.py
% ls helloworld.dist
_asyncio.so _lzma.so helloworld
_bisect.so _multibytecodec.so lib2to3
_blake2.so _multiprocessing.so libcrypto.1.1.dylib
_bz2.so _opcode.so libffi.7.dylib
_codecs_cn.so _pickle.so liblzma.5.dylib
_codecs_hk.so _posixshmem.so libncursesw.6.dylib
_codecs_iso2022.so _posixsubprocess.so libpanelw.6.dylib
_codecs_jp.so _queue.so libpython3.9.dylib
_codecs_kr.so _random.so libreadline.8.dylib
_codecs_tw.so _scproxy.so libsqlite3.0.dylib
_contextvars.so _sha512.so libssl.1.1.dylib
_crypt.so _socket.so libtcl8.6.dylib
_csv.so _sqlite3.so libtk8.6.dylib
_ctypes.so _ssl.so libz.1.dylib
_curses.so _statistics.so math.so
_curses_panel.so _struct.so mmap.so
_datetime.so _tkinter.so pyexpat.so
_dbm.so _uuid.so readline.so
_decimal.so _zoneinfo.so select.so
_elementtree.so array.so termios.so
_hashlib.so audioop.so unicodedata.so
_heapq.so binascii.so zlib.so
_json.so fcntl.so
_lsprof.so grp.so
実行例1 - すべてのモジュールも合わせてコンパイル
メインのプログラムのファイルだけでなく、インポートしているモジュールを含めたプログラム全体を再帰的にコンパイルしたい場合は、次のようにします。
code: bash
$ python -m nuitka --follow-imports helloworld.py
動的に読み込まれるファイルがあるソース・ディレクトリがある場合、つまり、通常のインポート・ステートメントの後にPYTHONPATHで指示されたパスを再検索しても見つからない場合、指定されたディレクトリを実行ファイルにも含めることを常に要求することができます。
code: bash
$ python -m nuitka --follow-imports --include-plugin-directory=plugin_dir program.py
動的なインポートを行わない場合は、コンパイル時にPYTHONPATHを設定するだけで構いません。
--include-plugin-directory は、Nuitka が予測できない __import__() 呼び出しを行っている場合にのみ使用してください。Nuitka はこのような場合にも警告を発し、このオプションを指摘します。
作成されるバイナリが期待どおりに実行できるかどうかは、CPythonと使用されるC拡張モジュールがインストールされているかどうかに依存します。
別のマシンにコピーできるようにしたい場合は、--standalone を使用して、作成された program.dist ディレクトリをコピーし、その中に置かれた program.exe(Windowsの場合)または program(他のプラットフォーム)を実行します。
実行例2 - 拡張モジュールのコンパイル
1つの拡張モジュールをコンパイルする場合は、次のように実行するだけです。
code: bash
$ python -m nuitka --module some_module.py
--moduleと--follow-imports オプションの両方が与えられた場合は、
オプション --follow-imports は動作しますが、含まれるモジュールがインポート可能になるのは、some_module をインポートした後になります。
できあがった拡張モジュールは、同じバージョンの CPython にしかロードできないことと、他の拡張モジュールがまれていないことに注意してください。
実行例3 - パッケージのコンパイル
パッケージ全体をコンパイルして、すべてのモジュールを埋め込むこともできます。Nuitkaを次のように実行します。
code: bash
$ python -m nuitka --module some_package --include-package=some_package
パッケージに含まれるディレクトリを再帰的に読み込むためには、明示的に指示する必要があります。そうしないとパッケージの中にあるデータファイルはまだ埋め込まれません。
実行例4 - プログラムの配布
他のシステムに配布するためには、--standaloneオプションを指定してフォルダを作成するスタンドアローンモードがあります。
code: bash
$ python -m nuitka --standalone program.py
このモードでは、インポートされているすべてのモジュールを埋め込むのがデフォルトです。--nofollow-import-to と指定すれば、モジュールを選択的に除外することができますが、プログラムの実行時に、除外したモジュールのインポートを行うと、ImportError 例外が発生します。
データファイルを取り込むには、オプション --include-data-file=<source>=<target> を使用します。source はファイルシステムのパスですが、target は相対パスで指定しなければなりません。スタンドアローンモードでは、手動でコピーすることもできますが、余分なチェックが行われることがあります。また、onefileモードでは、手動でのコピーはできません。
ディレクトリ内の一部または全部のファイルをコピーするには、オプション --include-data-file=/etc/*.txt=etc/ を使用します。ここでは、ファイルにシェルのワイルドカードでパターンを指定し、最後のスラッシュで示されるサブディレクトリにファイルを置くことができます。
すべてのファイルを含むフォルダ全体をコピーするためには、--include-data-dir=/path/to/images=images を使用することができます。このオプションでは、潜在的なサブディレクトリ構造を含むすべてのファイルがコピーされます。ここではフィルタリングはできません。つまり、部分的にしかコピーしたくない場合は、事前にファイルを削除するようにしてください。
パッケージのデータについては、--include-package-data を使えば、パッケージのデータファイルを自動的に検出してコピーすることができます。これはパッケージのデータファイルを自動的に検出してコピーするもので、シェルスタイルのパターンも受け付けます。
データファイルについては、ほとんど自分でやるしかありません。Nuitka は人気のあるパッケージで必要とされるものを追跡していますが、不完全な場合もあります。
これらがスタンドアローンモードでうまく動作すれば、必要に応じてワンファイルモードを使うことができます。
code: bash
$ python -m nuitka --onefile program.py
これを実行すると、単一のバイナリファイルが作成されます。Linuxでは、バイナリファイルは自分自身を解凍するのではなく、ファイルシステムとしてその内容をループバックマウントして使用します。
Windowsの場合、一時ファイルのディレクトリは、デフォルトではユーザーのものが使用されますが、--windows-onefile-tempdir-spec=%TEMP%\onefile_%PID%_%TIME%で指定されるパス指定で上書きすることができます。
現在、以下の拡張トークンが利用できます。
table: Windowsでの拡張トークン
トークン 説明 例
%TEMP% 使用する一時フォルダ C:Users...AppDataLocalsTemp
%PID% プロセスID 2772
%TIME% エポック時間 1299852985
%PROGRAM% 実行ファイルのフルパス(拡張子含む) C:SomeWhereYourOnefile.exe
Windowsでは、実行中のプログラムはOSからロックされてしまうため、固定のフォルダ名を使用することは可能ですが、プログラムが再起動された場合にロックの問題が発生する可能性があります。
通常、パスを一意にするためには、%TIME% または少なくとも %PID% を使用する必要があります。これは主に、自分で選択した場所や命名規則に従った場所に物を置いておきたい場合などの使用例を想定しています。
カスタマイズ
アイコンの設定
見栄えを良くするために、アイコンを設定することができます。Windowsでは、アイコンのファイル、テンプレートの実行ファイル、またはPNGファイルを設定することができます。これらは組み合わせることもできます。
code: bash
python -m nuitka --onefile --windows-icon-from-ico=your-icon.png program.py
python -m nuitka --onefile --windows-icon-from-ico=your-icon.ico program.py
python -m nuitka --onefile --windows-icon-template-exe=your-icon.ico program.py
スプラッシュスクリーン
スプラッシュスクリーン(splash screen)は、プログラムの起動が遅いときに便利です。Onefileモードで生成した実行バイナリの起動自体は遅くないのですが、プログラムが遅いかもしれませんし、使用しているコンピュータの速度にも依存するため、スプラッシュスクリーンがあった方が良いかもしれません。Nuitkaでは、Windows用に簡単に追加することができます。
スプラッシュスクリーンについては、PNGファイルとして指定する必要があります。そして、プログラムの準備ができたら、スプラッシュスクリーンを無効にするようにしてください。例えば、インポートを完了し、ウィンドウを準備し、データベースに接続して、スプラッシュスクリーンを消したい場合などです。ここでは、プロジェクト構文を使ってコードと作成物を組み合わせ、これをコンパイルします。
code: python
# nuitka-project: --onefile
# nuitka-project: --onefile-windows-splash-screen-image={MAIN_DIRECTORY}/Splash-Screen.png
print("Delaying startup by 10s...")
import time
time.sleep(10)
# Use this code to signal the splash screen removal.
if "NUITKA_ONEFILE_PARENT" in os.environ:
splash_filename = os.path.join(
tempfile.gettempdir(),
)
if os.path.exists(splash_filename):
os.unlink(splash_filename)
print("Done... splash should be gone.")
# ...
典型的な問題
動的なsys.path
コンパイルしようとしているスクリプトが sys.path を変更することがありえます。例えばソースコードを含むディレクトリを相対的に挿入した場合、現在のバージョンの Nuitka ではそれらを参照することができません。この場合は、環境変数 PYTHONPATHに変更後の値を設定すれば、コンパイルすることができます。
スタンドアローンでのデータファイルの欠落
例えば、VERSIONファイルのチェックでデフォルトがunknownになっているために、パッケージが正しいバージョンではないと訴えている場合があります。アイコンファイルやヘルプテキストがないと、おかしなエラーが発生することがあります。
ファイルが存在しない場合のエラーパスはバグを含んでいることが多く、結合されていないローカル変数のようなプログラミングエラーが発生することもあります。これらの例外が原因となっている可能性があることを念頭に置いて、注意深く見てください。スタンドアローンを使用せずにプログラムを動作させた場合、データファイルが原因である可能性があります。
スタンドアローンにDLLがない
Nuitkaには、DLLのコピーに対応するプラグインがあります。NumPy、SciPy、Tkinterなどです。
これらは、他のシステムで実行できるようにするための特別な処理が必要です。手動でコピーするだけでは不十分で、奇妙なエラーが発生します。新しいバージョンのパッケージ、特にNumPyはサポートされていないことがあります。このような場合には、古いバージョンを使用する必要があります。
スタンドアローンモードでの依存関係の悪化
いくつかのパッケージはシングルインポートですが、Nuitkaにとっては(文字通り)1000以上のパッケージをインクルードすることになります。典型的な例はPandasで、想像できるあらゆるものを接続して使用できるようにコンパイルを行います。そのため。想像できるすべてのものを明確にするため、コンパイルには時間がかかります。
Nuitkaは将来的に効果的なキャッシングを使うように対処しなければなりません。今のところは、依存関係を解決するために膨大なコンパイル時間が必要になっています。
今のところ、この問題に対応するためのanti-bloatプラグインは、必要のないインポートをブロックし、それが発生した場合にはエラーを出すことができます。
例えば、 --enable-plugin=anti-bloat --noinclude-pytest-mode=nofollow --noinclude-setuptools-mode=nofollow のように使用し、そのヘルプ出力を確認してください。例えば、スタンドアローンモードでは PyQt5 がアンインストールされているとみなされるようにするなどです。
ワンファイルモードでのファイルの検索
sys.argv[0]とメインモジュールの __file__ の間には、一時的な場所へのブートストラップを使用しているために、ワンファイルモードでは違いがあります。sys.argv[0]のは元の実行ファイルのパスで、__file__はブートストラップ実行ファイルが解凍された一時的または永久的なパスです。データファイルは後者の場所に、オリジナルの環境ファイルは前者の場所に置かれます。
実行ファイルの近くにあると思われるファイルと、ワンファイルモードによる実行バイナリの中にあると思われるファイルの2つのファイルがある場合、次のようにアクセスします。
code: python
# これは onefile.exe の近くにあるファイルを見つける
open(os.path.join(os.path.dirname(sys.argv0), "user-provided-file.txt")) # これは onefile.exe の中のファイルを見つける
open(os.path.join(os.path.dirname(__file__), "user-provided-file.txt"))
コンソールを使わないWindowsプログラムではエラーが出ない
コンパイル済みの実行ファイルをデバッグするためには、 --windows-disable-console を削除するか、 --windows-force-stdout-spec および --windows-force-stderr-spec オプションを、上記の --windows-onefile-tempdir-spec で説明したようなパスで使用してください。
コード中のオプション
条件付きのオプションや、事前に定義された変数を使ったオプションがサポートされています、これはその例です。
code: pyton
# Compilation mode, support OS specific.
# nuitka-project-if: {OS} in ("Windows", "Linux", "Darwin", "FreeBSD"):
# nuitka-project: --onefile
# nuitka-project-if: {OS} not in ("Windows", "Linux", "Darwin", "FreeBSD"):
# nuitka-project: --standalone
# The PySide2 plugin covers qt-plugins
# nuitka-project: --enable-plugin=pyside2
# nuitka-project: --include-qt-plugins=sensible,qml
コメントは行頭でなければならず、Pythonのように条件ブロックを終わらせるためにインデントが使用されます。現在のところ、上記のキーワード以外のキーワードはありません。
table: コード中でのオプションに使用されるキーワード
変数 説明 例
{OS} 使用するOS Linux, Windows, Darwin, FreeBSD, OpenBSD
{Version} Nuitkaのバージョン (0, 6, 14)
{Arch} 使用するアーキテクチャ x86_64, arm64, etc.
{MAIN_DIRECTORY} コンパイルされたファイルのディレクトリ some_dir/maybe_relative
Pythonコマンドラインフラグ
コンパイルしたプログラムに -O や -S のようなものを Python に渡すために、Nuitka にこれらのオプションをエミュレートさせる --python-flag= というコマンドラインオプションがあります。
Python のコマンドラインフラグのうち重要なものはサポートされていますが、さらに追加することも可能です。
コンパイル結果のキャッシュ
Cコンパイラは、同じ入力ファイルで起動した場合、何度もコンパイルするのに長い時間と多くのCPUを必要とします。gccを使用する際には、(Windowsであっても)ccacheがインストールされ、設定されていることを確認してください。例えば、プログラムを変更すると多くのCファイルが変更され、キャッシュされた結果を使用する代わりに新しいコンパイルが必要になることがあります。
Windowsでは、gccの場合、Nuitkaは公式のソースからダウンロードして自動的に実行する ccache.exeの使用できるようにしています。他のバージョンではハングアップする可能性があるため、Windowsではこの方法を推奨します。
Nuitka は、ぷらっとフォームのコマンド検索の PATH に ccache があれば、それを使用します。また、環境変数 NUITKA_CCACHE_BINARY にバイナリのフルパスを設定することで、提供することも可能です。これは CI システムで使用するためです。
MSVC コンパイラと ClangCL のセットアップでは、clcache の使用は自動的に行われ、Nuitka に含まれています。
キャッシュの保存場所の管理
ダウンロード、CやNuitkaからのキャッシュされたコンパイル結果など、あらゆる種類のキャッシュ結果の保存は、appdirsパッケージで決定されるプラットフォーム依存のディレクトリで行われます。しかし、環境変数 NUITKA_CACHE_DIR にベース・ディレクトリを設定することで、これを上書きすることができます。これは、ホーム・ディレクトリは永続化されないが、他のパスは永続化される環境で使用するためのものです。
ランナー
python -m nuitka を実行すると、あなたが考えているものを使用していることを 100% 確認できます。間違った Python を使うと、良いコードであれば SyntaxError が、インストールされたモジュールであれば ImportError が発生します。これは、Python2でPython3のコードを使ってNuitkaを実行した場合や、その逆の場合に起こります。同じ Python インタープリタバイナリを明示的に呼び出すことで、この問題を完全に回避できます。
最速の C コンパイラ
Windowsの64ビットPythonでのpystone.exeの最速バイナリは、MinGW64が非常に高速であることが証明されていて、およそ20%良いスコアです。そのため、MSVCではなく MinGW64 を使用することを推奨します。Clang7のclang-cl.exeを使うと、MSVCよりは速かったですが、それでもMinGW64よりはかなり遅いためお勧めできません。
Linuxのpystone.binでは、clang6で生成されたバイナリがgcc-6.3よりも高速でしたが、大きな差はありませんでした。gccはすでにインストールされていることが多いので、今のところはそちらを使うことをお勧めします。
なお、C言語のコンパイル時間の違いについてはまだ調べていません。
予期せぬ速度低下
標準のCPythonのようにPython DLLを使用すると、Unicode文字列を扱うコンパイルされていないコードなどで、予期せぬ速度低下を引き起こすことがあります。これは、DLLに常駐するのではなく、DLLを呼び出すことでオーバーヘッドが発生するからです。また、Pythonを1つのバイナリにまとめたものよりもDLL自体が遅くなることもあります。
そのため、可能であれば静的リンクを行うようにしてください。これは現在、Windows 以外のプラットフォームで実行される Anaconda Python でのみで可能です。
スタンドアローンの実行ファイルと依存関係
Windows 用のスタンドアローンモードで実行ファイルを作成するプロセスは、コンパイルされた実行ファイルと一緒に必要なライブラリを配布フォルダにコピーするために、外部の依存性ウォーカーを使用します。
依存関係に何かが欠けていることに気づく方法はいくらでもあります。手動でフォルダにコピーしてはいけません、特にDLLはコピーすることをしないでください。代わりにバグレポートを作成して、Nuitkaがこれらを適切に処理できるようにしてください。
リソースに関するWindowsのエラー
Windowsでは、Windows DefenderツールとWindows Indexing Serviceの両方が作成されたばかりのバイナリをスキャンしますが、Nuitkaはリソースの追加などの作業を行い、ロックを保持しているためにランダムに操作を阻止します。これらのサービスからコンパイルステージを除外するようにしてください。
Windowsスタンドアローンプログラムの再配布
MingGW64またはMSVCでコンパイルした場合でも、スタンドアロンプログラムはVisual C Runtimeライブラリへの外部依存性を持っています。Nuitkaは、お客様のシステムからそれらの依存するDLLを配布フォルダにコピーしようとします。
Microsoft Windows 10以降、Microsoftはapi-ms-crt-*.dllへの呼び出しを再フックするuclt.dll(Universal C Runtime libraries)を出荷しています。
以前のWindowsプラットフォーム(およびwine/ReactOS)では、Nuitkaのスタンドアロンコンパイルプログラムを実行する前に、Visual C Runtimeライブラリのインストールを検討する必要があります。
使用するCコンパイラに応じて、以下のバージョンが必要になります。
table: WindowsでのVisual CコンパイラのバージョンとCPython
バージョン リリース年 CPythonバージョン
14.2 2019 3.5, 3.6, 3.7, 3.8, 3.9
14.1 2017 3.5, 3.6, 3.7, 3.8
14.0 2015 3.5, 3.6, 3.7, 3.8
10.0 2010 3.3, 3.4
9.0 2008 2.6, 2.7
table: WindowsでのMinGW64コンパイラのバージョンとCPython
バージョン リリース年 CPythonバージョン
8.1.0 2015 3.5, 3.6, 3.7, 3.8, 3.9
最適化
Nuitka では以降で説明するような最適化を処理が行われます。
定数畳み込み
最適化の中でも最も重要なのは、定数畳み込み(constant folding)です。これは、ある操作がコンパイル時に完全に予測できる結果となる場合に定数に置き換えるというものです。単純な例を C言語で記述してみます。
code: C
a = 60 * 60 * 24
最近の多くのコンパイラでは、このコードをコンパイルするときに乗算命令を2回実行するようなことはしません。かわりに、定数 86400 をセットするように命令を生成します。
現在、Nuitkaはいくつかの組み込み関数でこれを行っており(まだすべてではありません)、例えば、二項/一項演算や比較などでこれを行っています。
code: python
5 + 6 # 二項演算
not 7 # 単項演算
5 < 6 # 比較演算
range(3) # 組み込み関数
リテラル(ソースコード中で使用される、数値や文字列等の直に示したデータのこと)は明らかに定数の源となるものですが、他にも定数の伝搬や関数のインライン化などの最適化ステップが必要になることがほとんどです。そのため、このステップは過小評価されるべきではなく、最適化を成功させるための非常に重要なステップです。定数を生成するためのあらゆるオプションは、生成されるコードの品質に大きな影響を与えます。
定数の伝搬
最適化の中核には、実行時の変数の値や代入の予測を決定する試みがあります。それは、その入力が定数であるか、似たような値であるかを判断するものです。高価な操作であるモジュール変数へのアクセスなどの式は、関数スコープのモジュール間で一定である可能性があり、その場合、モジュール変数のルックアップを一切行わないか、繰り返す必要はありません。
例えば、モジュールの属性 __name__は読み込まれるだけなので、その値はコンパイル時にわかる一定の文字列に予測することができます。この値は、定数畳み込みの入力として使用することができます。
code: python
if __name__ == "__main__":
# ...
use_something_not_use_by_program()
組み込みの名前参照
組み込み例外名の参照は,モジュールレベルの読み取り専用変数として使用されている場合には,最適化されます。
code: python
try:
something()
except ValueError: # ValueErrorは普通にグローバル変数なのでルックアップが遅い
pass
組み込み関数の予測
type、len、rangeのような組み込みの呼び出しでは、コンパイル時に結果を予測することができることがよくあります。Nuitkaは結果や発生した例外を単純に判断し、組み込み呼び出しをその値で置き換えることができ、より多くの定数畳み込みやコードパスの削減が可能になります。
code: python
type("string") # 予測可能な結果、組み込み型 str.
range(3, 9, 2) # 予測可能な結果
range(3, 9, 0) # 予測可能な例外 第3引数のゼロ(0)は range の例外を発生させる
結果が大きい場合、組み込みの結果を予測してはいけない場合があります。例えば,range() の呼び出しは,結果をバイナリに含めるには大きすぎる値になっているかもしれません。そのような場合は、予測を実行しません。
code: python
range(100000) # 展開するには大きすぎる
条件分岐の予測
条件文では、条件を予測することができないために、いくつかの分岐が実行されないことがあります。このような場合、分岐は行われず、条件のチェックは削除されます。
これにより、典型的には次のようなコードを予測することができます。
code: python
if __name__ == "__main__":
# ...
use_something_not_use_by_program()
あるいは、
code: python
if False:
# ...
use_something_not_use_by_program()
また、一定の伝搬の恩恵を受けたり、伝搬を有効にしたりすることができます。いくつかの枝が取り除かれると、他のことがより予測しやすくなるため、これが他の最適化を可能にするきっかけになるからです。
分岐枝を取り除くごとに、最適化の可能性が高まります。いくつかのコードブランチが削除されると、アクセスパターンがよりフレンドリーになる可能性があります。例えば、ある関数が削除されたブランチでしか呼び出されないとします。完全に削除することも可能ですが、それは他の結果にもつながるかもしれません。
例外の伝搬
コンパイル時に決定される例外については、例外を発生させるだけの式があります。このような例外は、潜在的な「副作用」、つまり例外が発生する前に実行され、まだ実行されなければならない式の一部を収集しながら、上方に伝搬することができます。
次のようなコードを考えてみましょう。
code: python
print(side_effect_having() + (1 / 0))
print(something_else())
(1 / 0) は ZeroDivisionErro r例外を発生させることが予測され、その例外は+ 演算で伝搬されます。この部分は通常通りの定数伝搬です。
side_effect_having() の呼び出しは保持されなければなりませんが、print() は保持されないので、明示的な例外発生に変えることができます。ステートメントシーケンスを中止することができるので、something_else() の呼び出しはコードの生成や検討が不要になります。
そのために、Nuitkaは例外を発生させる特別なノードを扱い、いわゆる side_effects式でラップされますが、コード内では値を持つ式として使用できます。
例外の範囲縮小
次のようなコードを考えてみましょう。
code: python
try:
b = 8
print(range(3, b, 0))
print("Will not be executed")
except ValueError as e:
print(e)
try ブロックが必要以上に大きく、b = 8 という文は ValueError 例外を発生させることはありません。そのため、何のリスクもなくtryの外側に移動することができます。
code: python
b = 8
try:
print(range(3, b, 0))
print("Will not be executed")
except ValueError as e:
print(e)
例外ブロックのインライニング
例外の伝搬により、このコードを変換することが可能になります。
code: python
try:
b = 8
print(range(3, b, 0))
print("Will not be executed!")
except ValueError as e:
print(e)
code: python
try:
raise ValueError("range() step argument must not be zero")
except ValueError as e:
print(e)
これは、例外の発生と捕捉を回避して、例外を作ることで複雑さを減らすことができます。
code: python
e = ValueError("range() step argument must not be zero")
print(e)
空のブロックの除去
効果のないコードしか含まれていないループや条件文については、構成要素全体を削除できるようにします。
code: python
for i in range(1000):
pass
このループは削除することができますし、より安全には i = 999とできます。
その他の例では
code: python
if side_effect_free:
pass
アンパッキングの予測
配列への代入の右辺の長さが予測できる場合、アンパックを複数の代入に置き換えることができます。
code: python
a, b, c = 1, side_effect_free(), 3
code: python
a = 1
b = side_effect_free()
c = 3
もちろん、これが本当に安全なのは、左辺が代入ターゲットの構築中に例外を発生させることができない場合だけです。
現在では、式が例外を発生させるかどうかを予測することができないため、定数に対してのみこれを行っています。
組み込み型推論
in xrange()や in range() のような構造が使われるとき、反復が何をするかを知り、それを表現することで、イテレータの代わりに使うことができます。
code: python
for i in range(1000):
something(i)
range(1000) を、より効率的に整数ループを行う特別なクラスのオブジェクトに変換することができます。i がそこからしか代入されない場合、これは専用クラスの良いケースになるかもしれません。
関数呼び出しの高速化
関数は、引数の解析や tp_call ハンドラが、実際の関数コードから独立しているように構成されています。こうすることで、呼び出しを最適化することができます。一つの問題は、評価順序が異なる場合があることです。
code: python
def f(a, b, c):
return a, b, c
f(c=get1(), b=get2(), a=get3())
この場合、最初に get1() を評価し、次に get2() を評価し、最後に get3() を評価し、これらの値で関数を呼び出す必要があります。
そのため、実際の呼び出しを行う前にパラメータのステージングを行い、get1()、get2()、get3() の呼び出しの順番が入れ替わらないようにする必要があります。
イテレートされたコンテナ型の下位化
リスト定数へのアクセスが、代わりにタプル定数になる場合があります。
次の例をみてみましょう。
code: python
something(x)
このコードは次のように最適化することができます。
code: python
for x in (a, b, c):
something(x)
これは、リストは可変型でそれを保証するためのチェックが必要ですが、タプルは明らかに不変であるため、よりシンプルで高速なコードを生成することができ、必要なチェックも少なくなります。これはセットに対しても可能です。
まとめ
Nuitka は独自の最適化を行っていますが、実際の起動にはオーバーヘッドが伴うことを理解しておきましょう。また、依存関係の多いモジュールを使用している場合、nuitka でのコンパイルにはかなり時間がかかることも留意しておいてください。
それでも、Pythonで単一ファイルとして配布したいといったときや、プログラムコードを隠蔽したいときなどでは、利用価値があるのではないでしょうか?
参考