【Flet】ボタンから外部プログラムを実行する【Python】

2024年12月14日Flet,Python

ボタンから外部プログラムを実行する

ボタンクリックからサブシステムを実行する。 ファイルを作成するプログラムとファイルを削除する単純な2つのプログラムを使ってサブプログラムの動き方を確認します。


ボタンクリックで外部プログラムを実行する①

外部プログラム

sub_01.py

  • カレントディレクトリに sub.txt を作成する
from pathlib import Path

file_path = Path.cwd() / 'sub.txt'
Path.touch(file_path)
print('ファイルを作成しました')

sub_02.py

  • カレントディレクトリの sub.txt を削除する
from pathlib import Path

file_path = Path.cwd() / 'sub.txt'
Path.unlink(file_path)
print('ファイルを削除しました')

呼び出し元のプログラム

概要

  • 外部プログラムの実行は関数(run_sub_program)で共有化する
import flet as ft
import subprocess
import sys
from pathlib import Path

def main(page: ft.Page):
    # 外部プログラムを実行する関数
    def run_sub_program(script_name):
        script_path = Path.cwd() / script_name
        try:
            result = subprocess.run([sys.executable, script_path], capture_output=True, text=True, errors='replace')
            print(f'Program {script_name} executed with return code {result.returncode}')
            print(result.stdout)
            print(result.stderr)
        except Exception as ex:
            print(f'Failed to run {script_name}: {ex}')

    # ボタンの作成
    button1 = ft.ElevatedButton('sub_01.py 実行', on_click=lambda _: run_sub_program('sub_01.py'))
    button2 = ft.ElevatedButton('sub_02.py 実行', on_click=lambda _: run_sub_program('sub_02.py'))

    # 画面サイズ
    page.window_width = 300
    page.window_height = 300

    # ページにボタンを追加
    page.add(button1, button2)

# アプリケーションを実行
ft.app(target=main)

実行結果

button1 クリック時

Program sub_01.py executed with return code 0
ファイルを作成しました

button2 クリック時

Program sub_02.py executed with return code 0
ファイルを削除しました

ファイルが無い状態でbutton2 クリック時(一部省略)

Program sub_02.py executed with return code 1
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。: 'C:\sub.txt'


※※※関数の呼び出し方が誤っているパターン※※※

button1 の設定誤り

  • run_sub_program('sub_01.py') がボタンが作成された時点で即座に実行されてしまう
  • 関数呼び出しを遅延させるためにラムダ関数を使用する必要がある
import flet as ft
import subprocess
import sys
from pathlib import Path

def main(page: ft.Page):
    # 外部プログラムを実行する関数
    def run_sub_program(script_name):
        script_path = Path.cwd() / script_name
        result = subprocess.run([sys.executable, script_path], capture_output=True, text=True, errors='replace')
        print(f'Program {script_name} executed with return code {result.returncode}')
        print(result.stdout)
        print(result.stderr)

    # ボタンの作成
    # NG:ボタンが作成されたタイミングで run_sub_program 関数が実行されてしまう
    button1 = ft.ElevatedButton('sub_01.py 実行', on_click=run_sub_program('sub_01.py'))
    # OK:ボタンがクリックされたときに run_sub_program 関数を実行する
    button2 = ft.ElevatedButton('sub_01.py 実行', on_click=lambda _: run_sub_program('sub_01.py'))
    button3 = ft.ElevatedButton('sub_02.py 実行', on_click=lambda _: run_sub_program('sub_02.py'))

    # 画面サイズ
    page.window_width = 300
    page.window_height = 300

    # ページにボタンを追加
    page.add(button1, button2, button3)

# アプリケーションを実行
ft.app(target=main)

ボタンクリックで外部プログラムを実行する②

呼び出し元のプログラム

概要

  • Fletのイベントハンドラはイベントオブジェクト(e)を引数として受け取る関数にする必要がある
  • ここでの run_sub_program 関数は内部で run(e) 関数を定義し、その関数を返すことでイベントに対応する
  • run(e) 関数のない直接定義の関数ではボタン作成時に関数が実行されてしまうので注意
import flet as ft
import subprocess
import sys
from pathlib import Path

def main(page: ft.Page):
    # 外部プログラムを実行する関数
    def run_sub_program(script_name):
        script_path = Path.cwd() / script_name
        def run(e):
            try:
                result = subprocess.run([sys.executable, script_path], capture_output=True, text=True, errors='replace')
                print(f'Program {script_name} executed with return code {result.returncode}')
                print(result.stdout)
                print(result.stderr)
            except Exception as ex:
                print(f'Failed to run {script_name}: {ex}')
        return run

    # ボタンの作成
    button1 = ft.ElevatedButton('sub_01.py 実行', on_click=run_sub_program('sub_01.py'))
    button2 = ft.ElevatedButton('sub_02.py 実行', on_click=run_sub_program('sub_02.py'))

    # 画面サイズ
    page.window_width = 300
    page.window_height = 300

    # ページにボタンを追加
    page.add(button1, button2)

# アプリケーションを実行
ft.app(target=main)

①と②の違いについて

外部プログラム実行関数の定義と呼び出し方

  • ①単純な関数を作成して直接呼び出している
    • lambdaon_click プロパティに渡す
  • ②はネストされた関数を作成して返している
    • ネストされた関数を返す関数で on_click プロパティに返された関数を渡す

クリックイベントのハンドリング

  • ①は run_sub_programlambda を通じて直接呼び出される
    • run_sub_program 自体がクリックイベントを受け取りすぐにスクリプトを実行する
  • ②は run_sub_program が返すネストされた関数がクリックイベントを受け取る
    • このネストされた関数は引数eを取る

使い分け

  • ①はシンプルでわかりやすい構造
  • ②はネストする分だけ複雑になるが、クリックイベントの処理を追加するなどの拡張がしやすい

ボタンクリックで外部プログラムを実行する③

呼び出し元のプログラム

概要

  • run_sub_program 関数をグローバルスコープに定義したパターン
  • main 関数以外の場所からも呼び出すことができる
  • main 関数のローカルな変数は参照できない
import flet as ft
import subprocess
import sys
from pathlib import Path

def run_sub_program(script_name):
    script_path = Path.cwd() / script_name
    def run(e):
        try:
            result = subprocess.run([sys.executable, script_path], capture_output=True, text=True, errors='replace')
            print(f'Program {script_name} executed with return code {result.returncode}')
            print(result.stdout)
            print(result.stderr)
        except Exception as ex:
            print(f'Failed to run {script_name}: {ex}')
    return run

def main(page: ft.Page):
    # ボタンの作成
    button1 = ft.ElevatedButton('sub_01.py 実行', on_click=run_sub_program('sub_01.py'))
    button2 = ft.ElevatedButton('sub_02.py 実行', on_click=run_sub_program('sub_02.py'))

    # 画面サイズ
    page.window_width = 300
    page.window_height = 300

    # ページにボタンを追加
    page.add(button1, button2)

# アプリケーションを実行
ft.app(target=main)

Flet,Python

Posted by junichi