Nuitkaを使ってスクリプトをバイナリ化してみよう Python は、作成したスクリプトをバイナリ化して配布したいといった機能がなりません。いくつかバイナリ化の機能を提供するツールもあったのですが、プラットフォームが限定されていたり、作成されるバイナリが大きすぎたりと、なかなかうまい解決策が見つけられませんでした。そうしたなか、に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 でインストールする方法を例示します。 プラットフォームに依存したインストーラやパッケージは次のURLからダウンロードすることができます。 nuitka をインストールすると、pip がインストールされている Python のバイナリパスに nuitka3 コマンドが配置されます。 あるいは、次のように Python モジュールとして実行してもかまいません。 --help オプションを与えると簡単な説明(それでもかなりの量)が出力されます。 Usage: nuitka3 [--module] [--run] [options] main_module.py nuitka3: error: no such option: --hepl (py39) goichiiisaka@GoichiMacBook answer % nuitka3 --help Usage: nuitka3 [--module] [--run] [options] main_module.py --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 --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 --warn-implicit-exceptions Enable warnings for implicit exceptions detected at --warn-unusual-code Enable warnings for unusual code detected at compile --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 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 --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. 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. Control the following into imported modules: --follow-stdlib Also descend into imported modules from standard library. This will increase the compilation time by a --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 --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 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 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. 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 Dump options for internal tree: --xml Dump the final result of optimization as XML, then --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. -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. Specify where intermediate and final output files should be put. The DIRECTORY will be populated with C files, object files, etc. Defaults to current --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 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 --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 Backend C compiler choice: --clang Enforce the use of clang. On Windows this requires a working Visual Studio version to piggy back on. -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 --quiet Disable all information outputs, but show warnings. --show-scons Operate Scons in non-quiet mode, showing the executed commands. Defaults to off. --show-progress Provide progress information and statistics. Defaults --no-progress Disable progress bar outputs (if tqdm is installed). --show-memory Provide memory information and statistics. Defaults to --show-modules Provide information for included modules and DLLs --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 --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 specifying a specific icon to be included, and all --windows-icon-from-exe=ICON_EXE_PATH Copy executable icons from this existing executable Request Windows User Control, to grant admin rights on execution. (Windows only). Defaults to off. Request Windows User Control, to enforce running from a few folders only, remote desktop access. (Windows --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. --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 --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 --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. --windows-onefile-tempdir-spec=ONEFILE_TEMPDIR_SPEC Use this as a temporary folder. Defaults to '%TEMP%\onefile_%PID%_%TIME%', i.e. system temporary --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-onefile-icon=ICON_PATH Add executable icon for onefile binary to use. Can be given only one time. Defaults to Python icon if --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. 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. --plugin-list Show list of all available plugins and exit. Defaults --user-plugin=USER_PLUGINS The file name of user plugin. Can be given multiple もうひとつ nuitka-run コマンドも使えるようになります。これは、 nuitka と同じですが、Pythonスクリプトをコンパイルして直接実行しようとします。 print(talk("Hello World")) if __name__ == "__main__": まず、Python でこのスクリプトを実行して動作することを確認しましょう。 次に、nuitka でこのコードをコンパイルしてみましょう。 % 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: Downloading 'https://nuitka.net/ccache/v4.2.1/ccache-4.2.1.zip'. 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 となります。 helloworld.bin helloworld.build helloworld.py bin 拡張子のファイルはコンパイル済みのバイナリで、直接実行することができます。コマンド検索のための環境変数 PATH を カレントディレクトリだけに置き換えて実行できています。 つまり、単独で実行することができるわけで、実行するために Python インタプリタは不要です。 ./helloworld.bin: Mach-O 64-bit executable x86_64 % env PATH=. helloworld.bin 少し考えるとわかると思いますが、実行には Python インタプリタは使用しませんが、スクリプト側で使用する(インポートする)Python ライブラリや、使用しているプラットフォームにインストールしているCライブラリは実行時に必要になります。 また、アーキテクチャの異なるマシンでは実行できません、 Linux dev01 3.10.0-1160.31.1.el7.x86_64 ./helloworld.bin: Mach-O 64-bit executable $ chmod 700 ./helloworld.bin -bash: ./helloworld.bin: cannot execute binary file コンパイル済みのバイナリを配布するには、 --standalone オプションを付けてビルドします。これで、単一の実行ファイルではなく、フォルダに出力されます。 hello.dist フォルダを相手のマシンにコピーして実行してください。 また、 --onefile オプションを与えると、単一のファイルを作成します。ただし、データファイルがない場合などにデバッグが困難になるため、 --onefile を使用する前には、 --standalone で期待どおりに動作することを確認するようにしてください。 helloworld.bin helloworld.dist helloworld.build helloworld.py _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 実行例1 - すべてのモジュールも合わせてコンパイル メインのプログラムのファイルだけでなく、インポートしているモジュールを含めたプログラム全体を再帰的にコンパイルしたい場合は、次のようにします。 $ python -m nuitka --follow-imports helloworld.py 動的に読み込まれるファイルがあるソース・ディレクトリがある場合、つまり、通常のインポート・ステートメントの後にPYTHONPATHで指示されたパスを再検索しても見つからない場合、指定されたディレクトリを実行ファイルにも含めることを常に要求することができます。 $ 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 (他のプラットフォーム)を実行します。 1つの拡張モジュールをコンパイルする場合は、次のように実行するだけです。 $ python -m nuitka --module some_module.py --module と --follow-imports オプションの両方が与えられた場合は、オプション --follow-imports は動作しますが、含まれるモジュールがインポート可能になるのは、 some_module をインポートした後になります。 できあがった拡張モジュールは、同じバージョンの CPython にしかロードできないことと、他の拡張モジュールがまれていないことに注意してください。 パッケージ全体をコンパイルして、すべてのモジュールを埋め込むこともできます。Nuitkaを次のように実行します。 $ python -m nuitka --module some_package --include-package=some_package パッケージに含まれるディレクトリを再帰的に読み込むためには、明示的に指示する必要があります。そうしないとパッケージの中にあるデータファイルはまだ埋め込まれません。 他のシステムに配布するためには、 --standalone オプションを指定してフォルダを作成するスタンドアローンモードがあります。 $ 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 は人気のあるパッケージで必要とされるものを追跡していますが、不完全な場合もあります。 これらがスタンドアローンモードでうまく動作すれば、必要に応じてワンファイルモードを使うことができます。 $ python -m nuitka --onefile program.py これを実行すると、単一のバイナリファイルが作成されます。Linuxでは、バイナリファイルは自分自身を解凍するのではなく、ファイルシステムとしてその内容をループバックマウントして使用します。 Windowsの場合、一時ファイルのディレクトリは、デフォルトではユーザーのものが使用されますが、 --windows-onefile-tempdir-spec=%TEMP%\onefile_%PID%_%TIME% で指定されるパス指定で上書きすることができます。 %TEMP% 使用する一時フォルダ C:Users...AppDataLocalsTemp
%PROGRAM% 実行ファイルのフルパス(拡張子含む) C:SomeWhereYourOnefile.exe
Windowsでは、実行中のプログラムはOSからロックされてしまうため、固定のフォルダ名を使用することは可能ですが、プログラムが再起動された場合にロックの問題が発生する可能性があります。 通常、パスを一意にするためには、 %TIME% または少なくとも %PID% を使用する必要があります。これは主に、自分で選択した場所や命名規則に従った場所に物を置いておきたい場合などの使用例を想定しています。 見栄えを良くするために、アイコンを設定することができます。Windowsでは、アイコンのファイル、テンプレートの実行ファイル、またはPNGファイルを設定することができます。これらは組み合わせることもできます。 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ファイルとして指定する必要があります。そして、プログラムの準備ができたら、スプラッシュスクリーンを無効にするようにしてください。例えば、インポートを完了し、ウィンドウを準備し、データベースに接続して、スプラッシュスクリーンを消したい場合などです。ここでは、プロジェクト構文を使ってコードと作成物を組み合わせ、これをコンパイルします。 print("Delaying startup by 10s...") if "NUITKA_ONEFILE_PARENT" in os.environ: splash_filename = os.path.join( "onefile_%d_splash_feedback.tmp" % int(os.environ["NUITKA_ONEFILE_PARENT"]), if os.path.exists(splash_filename): os.unlink(splash_filename) print("Done... splash should be gone.") コンパイルしようとしているスクリプトが sys.path を変更することがありえます。例えばソースコードを含むディレクトリを相対的に挿入した場合、現在のバージョンの Nuitka ではそれらを参照することができません。この場合は、環境変数 PYTHONPATHに変更後の値を設定すれば、コンパイルすることができます。 例えば、VERSIONファイルのチェックでデフォルトがunknownになっているために、パッケージが正しいバージョンではないと訴えている場合があります。アイコンファイルやヘルプテキストがないと、おかしなエラーが発生することがあります。 ファイルが存在しない場合のエラーパスはバグを含んでいることが多く、結合されていないローカル変数のようなプログラミングエラーが発生することもあります。これらの例外が原因となっている可能性があることを念頭に置いて、注意深く見てください。スタンドアローンを使用せずにプログラムを動作させた場合、データファイルが原因である可能性があります。 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つのファイルがある場合、次のようにアクセスします。 open(os.path.join(os.path.dirname(sys.argv[0]), "user-provided-file.txt")) 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 で説明したようなパスで使用してください。 条件付きのオプションや、事前に定義された変数を使ったオプションがサポートされています、これはその例です。 # 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のように条件ブロックを終わらせるためにインデントが使用されます。現在のところ、上記のキーワード以外のキーワードはありません。 {OS} 使用するOS Linux, Windows, Darwin, FreeBSD, OpenBSD
{Version} Nuitkaのバージョン (0, 6, 14)
{Arch} 使用するアーキテクチャ x86_64, arm64, etc.
{MAIN_DIRECTORY} コンパイルされたファイルのディレクトリ some_dir/maybe_relative
コンパイルしたプログラムに -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 インタープリタバイナリを明示的に呼び出すことで、この問題を完全に回避できます。 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 DefenderツールとWindows Indexing Serviceの両方が作成されたばかりのバイナリをスキャンしますが、Nuitkaはリソースの追加などの作業を行い、ロックを保持しているためにランダムに操作を阻止します。これらのサービスからコンパイルステージを除外するようにしてください。 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コンパイラに応じて、以下のバージョンが必要になります。 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
8.1.0 2015 3.5, 3.6, 3.7, 3.8, 3.9
Nuitka では以降で説明するような最適化を処理が行われます。 最適化の中でも最も重要なのは、定数畳み込み(constant folding)です。これは、ある操作がコンパイル時に完全に予測できる結果となる場合に定数に置き換えるというものです。単純な例を C言語で記述してみます。 最近の多くのコンパイラでは、このコードをコンパイルするときに乗算命令を2回実行するようなことはしません。かわりに、定数 86400 をセットするように命令を生成します。 現在、Nuitkaはいくつかの組み込み関数でこれを行っており(まだすべてではありません)、例えば、二項/一項演算や比較などでこれを行っています。 リテラル(ソースコード中で使用される、数値や文字列等の直に示したデータのこと)は明らかに定数の源となるものですが、他にも定数の伝搬や関数のインライン化などの最適化ステップが必要になることがほとんどです。そのため、このステップは過小評価されるべきではなく、最適化を成功させるための非常に重要なステップです。定数を生成するためのあらゆるオプションは、生成されるコードの品質に大きな影響を与えます。 最適化の中核には、実行時の変数の値や代入の予測を決定する試みがあります。それは、その入力が定数であるか、似たような値であるかを判断するものです。高価な操作であるモジュール変数へのアクセスなどの式は、関数スコープのモジュール間で一定である可能性があり、その場合、モジュール変数のルックアップを一切行わないか、繰り返す必要はありません。 例えば、モジュールの属性 __name__ は読み込まれるだけなので、その値はコンパイル時にわかる一定の文字列に予測することができます。この値は、定数畳み込みの入力として使用することができます。 if __name__ == "__main__": use_something_not_use_by_program() 組み込み例外名の参照は,モジュールレベルの読み取り専用変数として使用されている場合には,最適化されます。 type、len、rangeのような組み込みの呼び出しでは、コンパイル時に結果を予測することができることがよくあります。Nuitkaは結果や発生した例外を単純に判断し、組み込み呼び出しをその値で置き換えることができ、より多くの定数畳み込みやコードパスの削減が可能になります。 結果が大きい場合、組み込みの結果を予測してはいけない場合があります。例えば, range() の呼び出しは,結果をバイナリに含めるには大きすぎる値になっているかもしれません。そのような場合は、予測を実行しません。 条件文では、条件を予測することができないために、いくつかの分岐が実行されないことがあります。このような場合、分岐は行われず、条件のチェックは削除されます。 これにより、典型的には次のようなコードを予測することができます。 if __name__ == "__main__": use_something_not_use_by_program() use_something_not_use_by_program() また、一定の伝搬の恩恵を受けたり、伝搬を有効にしたりすることができます。いくつかの枝が取り除かれると、他のことがより予測しやすくなるため、これが他の最適化を可能にするきっかけになるからです。 分岐枝を取り除くごとに、最適化の可能性が高まります。いくつかのコードブランチが削除されると、アクセスパターンがよりフレンドリーになる可能性があります。例えば、ある関数が削除されたブランチでしか呼び出されないとします。完全に削除することも可能ですが、それは他の結果にもつながるかもしれません。 コンパイル時に決定される例外については、例外を発生させるだけの式があります。このような例外は、潜在的な「副作用」、つまり例外が発生する前に実行され、まだ実行されなければならない式の一部を収集しながら、上方に伝搬することができます。 print(side_effect_having() + (1 / 0)) (1 / 0) は ZeroDivisionErro r例外を発生させることが予測され、その例外は + 演算で伝搬されます。この部分は通常通りの定数伝搬です。 side_effect_having() の呼び出しは保持されなければなりませんが、 print() は保持されないので、明示的な例外発生に変えることができます。ステートメントシーケンスを中止することができるので、 something_else() の呼び出しはコードの生成や検討が不要になります。そのために、Nuitkaは例外を発生させる特別なノードを扱い、いわゆる side_effects 式でラップされますが、コード内では値を持つ式として使用できます。 print("Will not be executed") try ブロックが必要以上に大きく、 b = 8 という文は ValueError 例外を発生させることはありません。そのため、何のリスクもなくtryの外側に移動することができます。 print("Will not be executed") 例外の伝搬により、このコードを変換することが可能になります。 print("Will not be executed!") raise ValueError("range() step argument must not be zero") これは、例外の発生と捕捉を回避して、例外を作ることで複雑さを減らすことができます。 e = ValueError("range() step argument must not be zero") 効果のないコードしか含まれていないループや条件文については、構成要素全体を削除できるようにします。 このループは削除することができますし、より安全には i = 999 とできます。 配列への代入の右辺の長さが予測できる場合、アンパックを複数の代入に置き換えることができます。 a, b, c = 1, side_effect_free(), 3 もちろん、これが本当に安全なのは、左辺が代入ターゲットの構築中に例外を発生させることができない場合だけです。 現在では、式が例外を発生させるかどうかを予測することができないため、定数に対してのみこれを行っています。 in xrange() や in range() のような構造が使われるとき、反復が何をするかを知り、それを表現することで、イテレータの代わりに使うことができます。 range(1000) を、より効率的に整数ループを行う特別なクラスのオブジェクトに変換することができます。 i がそこからしか代入されない場合、これは専用クラスの良いケースになるかもしれません。関数は、引数の解析や tp_call ハンドラが、実際の関数コードから独立しているように構成されています。こうすることで、呼び出しを最適化することができます。一つの問題は、評価順序が異なる場合があることです。 f(c=get1(), b=get2(), a=get3()) この場合、最初に get1() を評価し、次に get2() を評価し、最後に get3() を評価し、これらの値で関数を呼び出す必要があります。 そのため、実際の呼び出しを行う前にパラメータのステージングを行い、 get1() 、 get2() 、 get3() の呼び出しの順番が入れ替わらないようにする必要があります。 リスト定数へのアクセスが、代わりにタプル定数になる場合があります。 これは、リストは可変型でそれを保証するためのチェックが必要ですが、タプルは明らかに不変であるため、よりシンプルで高速なコードを生成することができ、必要なチェックも少なくなります。これはセットに対しても可能です。 Nuitka は独自の最適化を行っていますが、実際の起動にはオーバーヘッドが伴うことを理解しておきましょう。また、依存関係の多いモジュールを使用している場合、nuitka でのコンパイルにはかなり時間がかかることも留意しておいてください。 それでも、Pythonで単一ファイルとして配布したいといったときや、プログラムコードを隠蔽したいときなどでは、利用価値があるのではないでしょうか?