py2appで日本語エラーになる問題を
解決した話
(ChatSpooler開発記)

py2appでビルドしたmacOSアプリが日本語で文字化けしてAppleScriptが動かない問題に遭遇。原因はpy2app環境でデフォルトエンコーディングがASCIIになること。main.pyでUTF-8を強制設定し、subprocess実行時にencoding=’utf-8’を明示したら解決しました。同じ症状で困ってる方の参考になれば

関連記事

1. はじめに

macOSアプリ「ChatSpooler」をPythonで開発していたところ、py2app1でビルドしたアプリが予期しない動作を見せました。開発中は正常に動作していたペースト機能が、アプリ版では全く動作しなくなったのです。

この記事では、py2app環境特有の文字エンコーディング問題と、その解決過程で分かった対処法を共有します。

py2app日本語エンコーディング問題解決 問題発生 開発環境: 正常動作 py2appビルド版: エラー 根本原因 py2app環境 デフォルト: ASCII 日本語エラーメッセージ → 文字化け発生 解決 UTF-8強制設定 エラーハンドリング ビルド設定修正 解決手順 1 main.pyでUTF-8強制設定 2 subprocess.runでencoding指定 3 setup.pyでrubicon除外 重要なコード修正 os.environ[‘LANG’] = ‘ja_JP.UTF-8’ subprocess.run([‘osascript’, ‘-e’, script], encoding=’utf-8′, errors=’replace’) ‘excludes’: [‘rubicon’] # setup.py

1.1. 問題の発見

ChatSpoolerは、AIチャットの自動送信を支援するmacOSアプリです。AppleScriptを使ってクリップボード操作やキー入力を制御し、テキストの自動送信を行います。

Py 問題の発見 開発環境 python main.py AppleScript正常実行 日本語テキスト送信OK VS py2appビルド版 ChatSpooler.app ペースト機能停止 AppleScriptエラー 調査で判明した事実 1 アクセシビリティ権限は正常に設定済み 2 AppleScriptのエラーメッセージに日本語が含まれる 3 py2app環境でのエンコーディング処理が原因と判明

開発中、python main.pyで起動したときは完璧に動作していました。しかし、py2appでビルドしたChatSpooler.appでは、送信機能が全く働きません。ログを確認すると、AppleScriptの実行でエラーが発生していることが判明しました。

最初はアクセシビリティ権限の問題を疑いました。システム環境設定でChatSpooler.appにアクセシビリティ権限を付与しても状況は変わりません。エラーメッセージを詳しく調べてみると、根本的な原因は別のところにありました。

1.2. 文字エンコーディングが原因だった

詳細なログを追跡した結果、AppleScriptのエラーメッセージに日本語が含まれており2、py2app環境ではこれを正しく処理できていないことが分かりました3。py2appでビルドしたアプリは、デフォルトでASCIIエンコーディングを使用するため、日本語を含むエラーメッセージで文字化けが発生していたのです4

subprocess.run(
    ['osascript', '-e', script], 
    capture_output=True, 
    text=True,
    encoding='utf-8',
    errors='replace'
)Code language: PHP (php)

UTF-8 ASCII 文字化け 文字エンコーディングが原因だった 根本原因の発見 開発環境 デフォルト:UTF-8エンコーディング py2app環境 デフォルト:ASCIIエンコーディング 問題発生の流れ 1 AppleScriptエラーに日本語メッセージ 2 py2app環境でエラーメッセージ文字化け 3 処理が停止、機能不全に 身近な例で説明 家の電気スイッチは正常に動作 外出先では同じスイッチが無効 環境の違いが原因

これは、開発者にとって見落としやすい問題です。開発環境では通常UTF-8が設定されているため、同じコードでも異なる動作を示します。まるで家の電気が正常に点くのに、外出先では同じスイッチが機能しないような状況でした。

2. エンコーディング問題の解決

1 エンコーディング問題の解決 1 main.pyでUTF-8強制設定 os.environ[‘LANG’] = ‘ja_JP.UTF-8’ sys.stdout.reconfigure(encoding=’utf-8′) 2 AppleScript実行部分の改善 subprocess.run([‘osascript’, ‘-e’, script], encoding=’utf-8′, # UTF-8明示 errors=’replace’) # エラー時文字置換 3 クリップボード操作の予防対策 UTF-8エンコード確認とASCII変換フォールバック追加

2.1. main.pyでの強制設定

最初に、アプリ起動時にUTF-8エンコーディングを強制設定する処理を追加しました。main.pyの先頭で、既存のimport文より前に次のコードを配置します。

# エンコーディング設定(py2app対応)
import locale
import os
import sys

# UTF-8エンコーディングを強制
if sys.platform == "darwin":
    os.environ['LANG'] = 'ja_JP.UTF-8'
    os.environ['LC_ALL'] = 'ja_JP.UTF-8'
    
# Python標準出力のエンコーディング設定
if hasattr(sys.stdout, 'reconfigure'):
    try:
        sys.stdout.reconfigure(encoding='utf-8')
        sys.stderr.reconfigure(encoding='utf-8')
    except:
        pass
Code language: PHP (php)

この設定により、アプリ全体でUTF-8エンコーディングが使用されるようになります。

2.2. AppleScript実行部分の改善

次に、AppleScriptを実行するrun_scriptメソッドを修正しました。エンコーディングを明示的に指定し、エラーハンドリングを強化します。

@staticmethod
def run_script(script: str) -> tuple[bool, str]:
    """AppleScript実行(エンコーディング修正版)"""
    try:
        print(f"AppleScript実行開始: {script[:50]}...")
        
        # エンコーディングを明示的に指定
        result = subprocess.run(
            ['osascript', '-e', script], 
            capture_output=True, 
            text=True,
            encoding='utf-8',  # UTF-8を明示
            errors='replace',  # デコードエラー時は文字を置換
            timeout=10
        )
        
        success = result.returncode == 0
        output = result.stdout.strip() if result.stdout else ""
        error = result.stderr.strip() if result.stderr else ""
        
        print(f"AppleScript結果: success={success}")
        if error:
            print(f"AppleScript stderr: {error}")
        
        return success, output
        
    except subprocess.TimeoutExpired:
        print("AppleScript実行タイムアウト")
        return False, "タイムアウト"
    except UnicodeDecodeError as e:
        print(f"AppleScript文字エンコーディングエラー: {e}")
        return False, "文字エンコーディングエラー"
    except Exception as e:
        print(f"AppleScript実行例外: {str(e)}")
        return False, str(e)
Code language: PHP (php)

重要なポイントは、encoding='utf-8'errors='replace'の指定です。これにより、文字化けが発生してもアプリがクラッシュせず、適切にエラーハンドリングが行われます。

2.3. クリップボード操作の対策

クリップボード操作でも同様の問題が発生する可能性があるため、予防的な対策を追加しました。

def copy_text_with_verification(self, text: str, max_wait: float = None) -> bool:
    """クリップボードコピー完了を確認(エンコーディング対応)"""
    try:
        # UTF-8でエンコード
        text_utf8 = text.encode('utf-8').decode('utf-8')
        pyperclip.copy(text_utf8)
    except UnicodeEncodeError:
        # エンコードエラー時はASCII文字のみを使用
        text_ascii = text.encode('ascii', 'ignore').decode('ascii')
        pyperclip.copy(text_ascii)
        self.logger.log("警告: 日本語文字を含むためASCII変換しました")
    
    # 以下、既存のコード...
Code language: PHP (php)

この処理により、どのような文字が含まれていても安全にクリップボード操作が実行されます。

3. py2appビルド時の新たな問題

py2appビルド時の問題解決 新たな問題が発生 ImportError: No module named ‘rubicon’ 問題の背景 • rubiconはpyobjcの一部として使用されるモジュール • py2appが依存関係を自動解決する際に問題発生 • ChatSpoolerでは直接使用していない 解決策:setup.pyでrubicon除外 ‘excludes’: [ ‘rubicon’, # py2appビルドエラー回避 ], ビルド成功、アプリ正常動作確認

エンコーディング問題を解決した後、今度はpy2appのビルド自体でエラーが発生しました。

ImportError: No module named 'rubicon'
Code language: JavaScript (javascript)

rubiconモジュール5は、pyobjcの一部として使用されるモジュールですが、py2appが自動的に依存関係を解決する際に問題を引き起こしていました。

3.1. ビルド問題の解決

setup.pyのexcludesリストにrubiconを追加することで、この問題を回避できました。

'excludes': [
    'test', 'tests', 'unittest', 'doctest',
    'rubicon',  # py2appビルドエラー回避のため追加
],
Code language: PHP (php)

ChatSpoolerではrubiconの機能を直接使用していないため、除外しても動作に影響はありません。必要な機能はpyobjcの他の部分で提供されています。

4. 動作確認と効果・学んだポイント

修正後、py2appでビルドしたアプリでテスト送信を実行したところ、完全に動作することを確認できました。日本語を含むテキストでも文字化けせず、AppleScriptによる操作も正常に実行されます。

学んだポイント py2app環境の特殊性 開発時とビルド環境でエンコーディング設定が異なる 日本語アプリ開発での重要事項 明示的なエンコーディング指定が必須 効果的な問題解決アプローチ • 症状だけでなく発生条件を詳しく観察 • エラーメッセージから根本原因を特定 • 不要モジュール除外で依存関係問題回避

この修正により、開発環境とビルド環境での動作差がなくなり、安定したアプリケーションが完成しました。

py2app環境では、開発時とは異なるエンコーディング設定になることがあります。特に日本語を扱うアプリケーションでは、明示的なエンコーディング指定が重要です。

また、py2appの依存関係解決で問題が発生した場合、不要なモジュールを除外することで解決できるケースがあります。エラーメッセージを詳しく読み、根本原因を特定することが解決の鍵となります。

問題の症状だけでなく、発生条件を詳しく観察することで、より効果的な解決策を見つけることができました。

  1. py2app – Python to macOS Application Bundle – py2appの公式ドキュメント、ビルド設定とエラー対処法
  2. Python Unicode HOWTO – Pythonでの文字エンコーディング処理の公式ガイド
  3. macOS AppleScript Language Guide – AppleScript実行時のエラーハンドリングに関するApple公式資料
  4. subprocess — Subprocess management – subprocessモジュールでのエンコーディング指定方法
  5. pyobjc – Python-Objective-C bridge – macOSネイティブ機能との連携に関する公式ドキュメント
  6. Python locale module – システムロケール設定の公式リファレンス
  7. macOS App Sandboxing and Code Signing – macOSアプリの権限とセキュリティ設定に関するApple公式ガイド

  1. py2appは、PythonスクリプトをmacOSのネイティブアプリケーション(.appファイル)に変換するためのツールです。開発環境とビルド環境で異なる動作を示すことがあります。 – pyenvのPythonでpy2appを使うときに注意すること[M1対応]
  2. AppleScriptは、エラー時に日本語を含むメッセージを返すことがあり、これがASCII環境で文字化けの原因となります。適切なエンコーディング処理が必要です。 – この処理Pythonでどう書く? – エムスリーテックブログ
  3. subprocessモジュールでは、encoding=’utf-8’とerrors=’replace’を指定することで、文字エンコーディングエラーを回避できます。errors=’replace’は、デコードできない文字を置換文字に変換します。 – Python の subprocess
  4. py2app環境では、Pythonの標準エンコーディングがASCIIに設定されることがあり、日本語を含むテキストの処理で問題が発生します。 – PythonでMac上にスタンドアロンアプリをつくってハマった件
  5. rubiconは、Objective-CとPythonをブリッジするライブラリの一部ですが、py2appのビルド時に依存関係の問題を引き起こすことがあります。ChatSpoolerでは直接使用していないため、除外しても問題ありません。 – PythonアプリのmacOS向けビルドで遭遇した起動エラーと解決策