py2appでビルドしたmacOSアプリが日本語で文字化けしてAppleScriptが動かない問題に遭遇。原因はpy2app環境でデフォルトエンコーディングがASCIIになること。main.pyでUTF-8を強制設定し、subprocess実行時にencoding=’utf-8’を明示したら解決しました。同じ症状で困ってる方の参考になれば
1. はじめに
macOSアプリ「ChatSpooler」をPythonで開発していたところ、py2app1でビルドしたアプリが予期しない動作を見せました。開発中は正常に動作していたペースト機能が、アプリ版では全く動作しなくなったのです。
この記事では、py2app環境特有の文字エンコーディング問題と、その解決過程で分かった対処法を共有します。
1.1. 問題の発見
ChatSpoolerは、AIチャットの自動送信を支援するmacOSアプリです。AppleScriptを使ってクリップボード操作やキー入力を制御し、テキストの自動送信を行います。
開発中、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が設定されているため、同じコードでも異なる動作を示します。まるで家の電気が正常に点くのに、外出先では同じスイッチが機能しないような状況でした。
2. エンコーディング問題の解決
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'
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は、PythonスクリプトをmacOSのネイティブアプリケーション(.appファイル)に変換するためのツールです。開発環境とビルド環境で異なる動作を示すことがあります。 – pyenvのPythonでpy2appを使うときに注意すること[M1対応]
- AppleScriptは、エラー時に日本語を含むメッセージを返すことがあり、これがASCII環境で文字化けの原因となります。適切なエンコーディング処理が必要です。 – この処理Pythonでどう書く? – エムスリーテックブログ
- subprocessモジュールでは、encoding=’utf-8’とerrors=’replace’を指定することで、文字エンコーディングエラーを回避できます。errors=’replace’は、デコードできない文字を置換文字に変換します。 – Python の subprocess
- py2app環境では、Pythonの標準エンコーディングがASCIIに設定されることがあり、日本語を含むテキストの処理で問題が発生します。 – PythonでMac上にスタンドアロンアプリをつくってハマった件
- rubiconは、Objective-CとPythonをブリッジするライブラリの一部ですが、py2appのビルド時に依存関係の問題を引き起こすことがあります。ChatSpoolerでは直接使用していないため、除外しても問題ありません。 – PythonアプリのmacOS向けビルドで遭遇した起動エラーと解決策