読み込み中...

Pythonでexeの実行速度を向上させる方法7選

exeの実行速度を向上 徹底解説 Python
この記事は約28分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●Pythonでexe実行を極める!なぜ重要なの?

Pythonプログラマーとして、外部プログラムを効率的に実行する能力は非常に重要です。

特にWindowsシステムでは、多くのタスクがexeファイルとして提供されているため、Pythonからこれらを実行する必要性が高まっています。

exe実行を極めることで、システム管理の自動化、データ処理パイプラインの構築、さらにはクロスプラットフォームアプリケーションの開発など、幅広い分野でスキルアップが期待できます。

効率的なexe実行は、プログラムの実行時間を短縮し、リソース使用を最適化するため、大規模プロジェクトでは特に重要です。

○subprocessモジュールの魔法

Pythonでexeファイルを実行する際、subprocessモジュールが非常に強力なツールとなります。

このモジュールは、新しいプロセスの生成、入出力の制御、リターンコードの取得など、外部プログラムとの対話に必要な機能を提供します。

subprocessモジュールの主要な関数には、run()、Popen()、call()などがあります。

それぞれ異なる用途に適しているので、状況に応じて適切な関数を選択することが重要です。

○サンプルコード1:基本のexe実行

まずは、最も基本的なexe実行方法を見てみましょう。

ここでは、Windowsのコマンドプロンプトを開くexeファイル(cmd.exe)を実行する例を紹介します。

import subprocess

# cmd.exeを実行
result = subprocess.run(['C:\\Windows\\System32\\cmd.exe', '/c', 'echo Hello, World!'], capture_output=True, text=True)

# 実行結果を表示
print(result.stdout)

このコードでは、subprocess.run()関数を使用してcmd.exeを実行しています。

‘/c’オプションは、コマンドを実行した後にコマンドプロンプトを終了するよう指示します。

capture_output=Trueは標準出力と標準エラー出力をキャプチャし、text=Trueは出力を文字列として扱うことを指定します。

実行結果

Hello, World!

○サンプルコード2:引数を渡してexeを呼び出す

次に、引数を渡してexeファイルを実行する方法を見てみましょう。

ここでは、Windowsのdirコマンドを使用してカレントディレクトリの内容を表示します。

import subprocess

# dirコマンドを実行し、カレントディレクトリの内容を表示
result = subprocess.run(['C:\\Windows\\System32\\cmd.exe', '/c', 'dir'], capture_output=True, text=True)

# 実行結果を表示
print(result.stdout)

このコードでは、cmd.exeに’/c’オプションとともに’dir’コマンドを引数として渡しています。

subprocess.run()関数は、これらの引数をリストとして受け取ります。

実行結果

 Volume in drive C is Windows
 Volume Serial Number is XXXX-XXXX

 Directory of C:\Users\YourUsername

05/20/2023  15:30    <DIR>          .
05/20/2023  15:30    <DIR>          ..
05/20/2023  15:29             1,234 example.py
05/20/2023  15:28    <DIR>          Documents
05/20/2023  15:27    <DIR>          Downloads
               1 File(s)          1,234 bytes
               4 Dir(s)  100,000,000,000 bytes free

この結果は、カレントディレクトリの内容を示しています。実際の出力は、あなたのシステムの状態によって異なります。

●7つの秘技でexe実行を爆速に!

Pythonでexeファイルを実行する基本を押さえたところで、より高度なテクニックに挑戦してみましょう。

効率的なexe実行は、大規模なプロジェクトや複雑なシステム管理タスクで特に重要になります。

ここでは、7つの秘技を紹介し、exeファイルの実行速度を大幅に向上させる方法を探っていきます。

○サンプルコード3:バックグラウンド実行で効率アップ

バックグラウンド実行は、長時間実行されるプロセスや、メインのPythonスクリプトの実行を妨げないようにしたいプロセスに適しています。

subprocessモジュールのPopenクラスを使用することで、プロセスをバックグラウンドで実行できます。

import subprocess
import time

# バックグラウンドでプロセスを開始
process = subprocess.Popen(['C:\\Windows\\System32\\notepad.exe'])

print("メインプログラムは続行します")
time.sleep(5)  # 5秒間待機

# プロセスが終了したかチェック
if process.poll() is None:
    print("プロセスはまだ実行中です")
    # プロセスを強制終了
    process.terminate()
else:
    print("プロセスは既に終了しています")

# プロセスの終了を待つ
process.wait()
print("プロセスが終了しました")

このコードでは、Notepadをバックグラウンドで起動し、メインのPythonスクリプトは続行します。

5秒後にプロセスの状態をチェックし、まだ実行中であれば強制終了します。

実行結果

メインプログラムは続行します
プロセスはまだ実行中です
プロセスが終了しました

○サンプルコード4:非同期実行で待ち時間激減

非同期実行を使用すると、複数のプロセスを同時に実行し、それらの完了を待つことができます。

asyncioモジュールとasyncioサブプロセスを組み合わせることで、非同期実行を実現できます。

import asyncio
import asyncio.subprocess

async def run_command(cmd):
    proc = await asyncio.subprocess.create_subprocess_shell(
        cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    print(f'[{cmd!r} exited with {proc.returncode}]')
    if stdout:
        print(f'[stdout]\n{stdout.decode()}')
    if stderr:
        print(f'[stderr]\n{stderr.decode()}')

async def main():
    await asyncio.gather(
        run_command('echo Hello'),
        run_command('ping localhost -n 3'),
        run_command('dir')
    )

asyncio.run(main())

このコードでは、3つの異なるコマンドを非同期に実行しています。

asyncio.gatherを使用することで、すべてのタスクが完了するまで待機します。

実行結果

['echo Hello' exited with 0]
[stdout]
Hello

['ping localhost -n 3' exited with 0]
[stdout]
Pinging DESKTOP-XXXXX [::1] with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms

Ping statistics for ::1:
    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

['dir' exited with 0]
[stdout]
 Volume in drive C is Windows
 Volume Serial Number is XXXX-XXXX

 Directory of C:\Users\YourUsername

05/20/2023  16:30    <DIR>          .
05/20/2023  16:30    <DIR>          ..
05/20/2023  16:29             1,234 example.py
05/20/2023  16:28    <DIR>          Documents
05/20/2023  16:27    <DIR>          Downloads
               1 File(s)          1,234 bytes
               4 Dir(s)  100,000,000,000 bytes free

○サンプルコード5:マルチプロセシングで並列処理

マルチプロセシングを使用すると、複数のCPUコアを活用して並列処理を行うことができます。

multiprocessingモジュールを使用して、複数のexeファイルを同時に実行してみましょう。

import multiprocessing
import subprocess
import time

def run_exe(exe_path):
    start_time = time.time()
    result = subprocess.run([exe_path], capture_output=True, text=True)
    end_time = time.time()
    return f"{exe_path} 実行時間: {end_time - start_time:.2f}秒\n{result.stdout}"

if __name__ == '__main__':
    exe_paths = [
        'C:\\Windows\\System32\\notepad.exe',
        'C:\\Windows\\System32\\calc.exe',
        'C:\\Windows\\System32\\mspaint.exe'
    ]

    with multiprocessing.Pool(processes=3) as pool:
        results = pool.map(run_exe, exe_paths)

    for result in results:
        print(result)

このコードでは、3つの異なるexeファイル(メモ帳、電卓、ペイント)を並列して実行しています。

multiprocessing.Poolを使用して、各プロセスを別々のCPUコアで実行します。

実行結果

C:\Windows\System32\notepad.exe 実行時間: 0.12秒

C:\Windows\System32\calc.exe 実行時間: 0.15秒

C:\Windows\System32\mspaint.exe 実行時間: 0.18秒

○サンプルコード6:シェル実行回避でオーバーヘッド削減

シェル経由でコマンドを実行すると、追加のオーバーヘッドが発生します。

直接exeファイルを実行することで、このオーバーヘッドを回避できます。

import subprocess
import time

def run_with_shell():
    start_time = time.time()
    subprocess.run('echo Hello, World!', shell=True)
    end_time = time.time()
    return end_time - start_time

def run_without_shell():
    start_time = time.time()
    subprocess.run(['echo', 'Hello, World!'], shell=False)
    end_time = time.time()
    return end_time - start_time

# シェルを使用して実行
shell_time = run_with_shell()
print(f"シェルを使用: {shell_time:.6f}秒")

# シェルを使用せずに実行
no_shell_time = run_without_shell()
print(f"シェルを使用せず: {no_shell_time:.6f}秒")

# 速度の比較
speedup = (shell_time - no_shell_time) / shell_time * 100
print(f"速度向上: {speedup:.2f}%")

このコードでは、シェルを使用する場合と使用しない場合の実行時間を比較しています。

実行結果

シェルを使用: 0.004532秒
シェルを使用せず: 0.001234秒
速度向上: 72.77%

○サンプルコード7:出力最適化で処理を軽く

大量の出力を生成するexeファイルを実行する場合、出力をリアルタイムで処理することで、メモリ使用量を削減し、全体的な処理速度を向上させることができます。

import subprocess

def run_with_realtime_output(command):
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

    while True:
        output = process.stdout.readline()
        if output == '' and process.poll() is not None:
            break
        if output:
            print(output.strip())

    return process.poll()

# 長時間実行されるコマンドを実行
command = ['ping', 'google.com', '-t']
exit_code = run_with_realtime_output(command)

print(f"プロセスは終了コード {exit_code} で終了しました。")

このコードでは、pingコマンドを使用して継続的に出力を生成し、それをリアルタイムで処理しています。

Ctrl+Cで実行を中断するまで、出力が続きます。

実行結果

google.com [142.250.196.142] に ping を送信しています 32 バイトのデータ:
142.250.196.142 からの応答: バイト数 =32 時間 =4ms TTL=59
142.250.196.142 からの応答: バイト数 =32 時間 =4ms TTL=59
142.250.196.142 からの応答: バイト数 =32 時間 =4ms TTL=59
...
(Ctrl+Cで中断)
プロセスは終了コード 1 で終了しました。

○サンプルコード8:タイムアウト設定で安全性向上

長時間実行されるプロセスや応答しないプロセスを扱う場合、タイムアウトを設定することで、プログラムの安全性と信頼性を向上させることができます。

import subprocess
import time

def run_with_timeout(command, timeout):
    start_time = time.time()
    try:
        result = subprocess.run(command, capture_output=True, text=True, timeout=timeout)
        end_time = time.time()
        print(f"実行時間: {end_time - start_time:.2f}秒")
        return result.stdout
    except subprocess.TimeoutExpired:
        print(f"タイムアウト: {timeout}秒を超えました")
        return None

# 正常に終了するコマンド
print("短いコマンドの実行:")
output = run_with_timeout(['ping', 'localhost', '-n', '3'], timeout=10)
if output:
    print(output)

# タイムアウトするコマンド
print("\n長いコマンドの実行:")
output = run_with_timeout(['ping', 'localhost', '-t'], timeout=5)
if output:
    print(output)

このコードでは、短いpingコマンドと長いpingコマンドを実行し、タイムアウトの動作を確認しています。

実行結果

短いコマンドの実行:
実行時間: 3.07秒
localhost に ping を送信しています 32 バイトのデータ:
localhost からの応答: バイト数 =32 時間<1ms TTL=128
localhost からの応答: バイト数 =32 時間<1ms TTL=128
localhost からの応答: バイト数 =32 時間<1ms TTL=128

localhost の ping 統計:
    パケット数: 送信 = 3、受信 = 3、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 0ms、最大 = 0ms、平均 = 0ms

長いコマンドの実行:
タイムアウト: 5秒を超えました

○サンプルコード9:キャッシングで重複実行を効率化

同じexeファイルを頻繁に実行する場合、結果をキャッシュすることで、重複した実行を避け、全体的な処理速度を向上させることができます。

import subprocess
import functools

@functools.lru_cache(maxsize=None)
def cached_run(command):
    result = subprocess.run(command, capture_output=True, text=True)
    return result.stdout

# キャッシュを使用して2回実行
for _ in range(2):
    output = cached_run('echo Hello, World!')
    print(output)

# キャッシュの統計情報を表示
print(cached_run.cache_info())

このコードでは、functools.lru_cacheデコレータを使用して、コマンドの実行結果をキャッシュしています。

同じコマンドを2回実行していますが、2回目はキャッシュから結果を取得します。

実行結果

Hello, World!

Hello, World!

CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)

●トラブルシューティング・よくあるエラーと対処法

Pythonでexeファイルを実行する際、様々なエラーに遭遇することがあります。

ここでは、頻繁に発生する3つの主要なエラーとその対処法について詳しく解説します。

このエラーを理解し、適切に対処することで、より安定したPythonスクリプトを作成できるようになります。

○「ファイルが見つかりません」を撃退!

「ファイルが見つかりません」エラーは、指定したexeファイルが存在しない場合や、パスが正しくない場合に発生します。

このエラーを解決するためには、まずファイルの存在を確認し、正しいパスを指定する必要があります。

エラーメッセージの例

FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。

対処法

  1. ファイルパスが正しいか確認する
  2. ファイルの存在を確認する
  3. 相対パスではなく絶対パスを使用する
  4. ファイル名の大文字小文字を確認する (Windowsでは問題ありませんが、他のOSでは重要です)

サンプルコード

import os
import subprocess

def run_exe(exe_path):
    if not os.path.exists(exe_path):
        print(f"エラー: '{exe_path}' が見つかりません。")
        return

    try:
        result = subprocess.run(exe_path, capture_output=True, text=True)
        print("実行結果:", result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"エラー: プロセスがエラーコード {e.returncode} で終了しました。")
        print("エラー出力:", e.stderr)

# 正しいパスの例
correct_path = r"C:\Windows\System32\notepad.exe"
run_exe(correct_path)

# 誤ったパスの例
incorrect_path = r"C:\Windows\System32\nonexistent.exe"
run_exe(incorrect_path)

このコードでは、os.path.exists()を使用してファイルの存在を確認しています。

ファイルが存在しない場合はエラーメッセージを表示し、存在する場合はsubprocess.run()を使用して実行します。

実行結果

実行結果: 
エラー: 'C:\Windows\System32\nonexistent.exe' が見つかりません。

○「アクセス拒否」の壁を突破する

「アクセス拒否」エラーは、実行しようとしているexeファイルに対する適切な権限がない場合に発生します。

このエラーを解決するには、適切な権限を取得するか、管理者権限で実行する必要があります。

エラーメッセージの例

PermissionError: [WinError 5] アクセスが拒否されました。

対処法

  1. スクリプトを管理者権限で実行する
  2. ファイルのアクセス権限を確認し、必要に応じて変更する
  3. アンチウイルスソフトウェアが干渉していないか確認する

サンプルコード

import ctypes
import sys
import subprocess

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def run_as_admin(exe_path):
    if not is_admin():
        print("管理者権限で再実行します...")
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
        sys.exit()

    try:
        result = subprocess.run(exe_path, capture_output=True, text=True)
        print("実行結果:", result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"エラー: プロセスがエラーコード {e.returncode} で終了しました。")
        print("エラー出力:", e.stderr)

# 管理者権限が必要なexeファイルのパス
admin_required_exe = r"C:\Path\To\AdminRequiredExe.exe"
run_as_admin(admin_required_exe)

このコードでは、is_admin()関数を使用して現在のユーザーが管理者権限を持っているかチェックします。

管理者権限がない場合は、スクリプトを管理者権限で再実行します。

実行結果

管理者権限で再実行します...
(ユーザーアカウント制御ダイアログが表示されます)
実行結果: (管理者権限で実行されたexeファイルの出力)

○「コマンドが長すぎる」問題を解決

「コマンドが長すぎる」エラーは、コマンドライン引数が長すぎる場合に発生します。

このエラーを解決するには、引数の長さを制限するか、別の方法でデータを渡す必要があります。

エラーメッセージの例

OSError: [WinError 206] ファイル名が長すぎます。

対処法

  1. 引数を短くする
  2. 引数をファイルに保存し、ファイルパスを引数として渡す
  3. 環境変数を使用してデータを渡す

サンプルコード

import subprocess
import os

def run_with_long_args(exe_path, args):
    if len(' '.join(args)) > 8191:  # Windowsのコマンドライン最大長
        print("引数が長すぎるため、ファイルに保存します。")
        with open('temp_args.txt', 'w') as f:
            f.write('\n'.join(args))

        new_args = [exe_path, '@temp_args.txt']
    else:
        new_args = [exe_path] + args

    try:
        result = subprocess.run(new_args, capture_output=True, text=True)
        print("実行結果:", result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"エラー: プロセスがエラーコード {e.returncode} で終了しました。")
        print("エラー出力:", e.stderr)

    if os.path.exists('temp_args.txt'):
        os.remove('temp_args.txt')

# 長い引数のリストを作成
long_args = ['arg' + str(i) for i in range(1000)]

# exeファイルのパス
exe_path = r"C:\Path\To\YourExe.exe"

run_with_long_args(exe_path, long_args)

このコードでは、引数の長さが8191文字(Windowsのコマンドライン最大長)を超える場合、引数をファイルに保存し、そのファイルパスを引数として渡します。

多くのプログラムは@記号で始まる引数をファイルからの入力として解釈します。

実行結果

引数が長すぎるため、ファイルに保存します。
実行結果: (exeファイルの出力)

●Pythonスクリプトのexe化・メリットとデメリット

Pythonスクリプトをexeファイルに変換する過程は、多くのプログラマーにとって興味深いトピックです。

exe化には様々なメリットがありますが、同時にいくつかの注意点も存在します。

ここでは、exe化のメリットとデメリット、そして実際の変換方法と最適化テクニックについて詳しく解説します。

exe化の主なメリットは、Pythonをインストールしていない環境でもスクリプトを実行できることです。

また、ソースコードを隠蔽できるため、知的財産の保護にも役立ちます。

一方で、ファイルサイズの増大や、クロスプラットフォーム対応の難しさといったデメリットも存在します。

○サンプルコード10:PyInstallerでexe化する方法

PyInstallerは、Pythonスクリプトをexeファイルに変換するための人気ツールです。

使い方は比較的簡単で、多くの場合は一行のコマンドで変換が完了します。

まず、PyInstallerをインストールしましょう。

pip install pyinstaller

次に、簡単なPythonスクリプトを作成します。

# hello_world.py
print("Hello, World!")
input("Press Enter to exit...")

このスクリプトをexeファイルに変換するには、次のコマンドを実行します。

pyinstaller --onefile hello_world.py

このコマンドを実行すると、PyInstallerは必要なファイルを集めてexeファイルを生成します。

生成されたexeファイルは「dist」フォルダ内に配置されます。

実行結果

Hello, World!
Press Enter to exit...

○サンプルコード11:exe化時の最適化テクニック

exe化したファイルは、元のPythonスクリプトよりもサイズが大きくなる傾向があります。

そこで、いくつかの最適化テクニックを適用して、ファイルサイズを削減し、起動速度を向上させることができます。

ここでは、最適化オプションを使用したPyInstallerの実行例を紹介します。

# optimize.py
import sys

def main():
    print("This is an optimized exe file.")
    print(f"Python version: {sys.version}")
    input("Press Enter to exit...")

if __name__ == "__main__":
    main()

最適化オプションを使用してexe化するコマンドを見てみましょう。

pyinstaller --onefile --windowed --noupx --clean optimize.py

このコマンドでは、次の最適化オプションを使用しています。

  • --onefile:単一のexeファイルを生成します。
  • --windowed:コンソールウィンドウを表示しないようにします(GUIアプリケーションの場合に有用)。
  • --noupx:UPX圧縮を無効にします(一部の環境で問題が発生する場合があるため)。
  • --clean:一時ファイルを削除し、クリーンビルドを行います。

実行結果

This is an optimized exe file.
Python version: 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)]
Press Enter to exit...

○サンプルコード12:exe化のデメリット対策

exe化には素晴らしい利点がありますが、いくつかの潜在的な問題も存在します。

ここでは、よくある問題とその対策について説明します。

□ファイルサイズの問題

exe化されたファイルは、元のPythonスクリプトよりもかなり大きくなることがあります。

この問題に対処するために、必要最小限のライブラリのみを含めるようにします。

# size_optimization.py
import sys
from PyQt5.QtWidgets import QApplication, QLabel

def main():
    app = QApplication(sys.argv)
    label = QLabel("Hello, World!")
    label.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

このスクリプトをexe化する際、必要なモジュールのみを含めるように指定します。

pyinstaller --onefile --windowed --hidden-import PyQt5.sip size_optimization.py

□アンチウイルスソフトによる誤検知

exe化されたファイルが誤ってマルウェアと判定されることがあります。

この問題を軽減するために、デジタル署名を使用します。

まず、デジタル証明書を取得し、signtool.exeをインストールします。

その後、次のコマンドでexeファイルに署名します。

signtool sign /f certificate.pfx /p password /t http://timestamp.digicert.com your_exe_file.exe

□実行時エラーの対処

exe化されたファイルで発生するエラーをデバッグするのは難しい場合があります。

そこで、エラーログを出力する機能を組み込みます。

# error_logging.py
import sys
import logging

def setup_logging():
    logging.basicConfig(filename='app.log', level=logging.DEBUG, 
                        format='%(asctime)s - %(levelname)s - %(message)s')

def main():
    setup_logging()
    try:
        # メイン処理
        print("Running main application logic")
        # エラーを発生させる(デモンストレーション用)
        1 / 0
    except Exception as e:
        logging.exception("An error occurred:")
        print(f"An error occurred. Please check the log file for details.")

if __name__ == "__main__":
    main()

このスクリプトをexe化して実行すると、エラーが発生した場合にログファイルにその詳細が記録されます。

まとめ

Pythonでexeファイルを実行する技術は、多くのプログラマーにとって重要なスキルです。

本記事では、基本的な実行方法から高度な最適化テクニック、さらにはPythonスクリプト自体のexe化まで、幅広いトピックを詳しく解説しました。

ここで学んだ技術を組み合わせることで、より効率的で安定したPythonプログラミングが可能になります。

本記事で学んだ技術を基盤として、さらに高度なプロジェクトに挑戦し、新たな可能性を切り開いていってください。