はじめに
WebブラウザでPythonを動作させる強力なツールとしてPyodideが注目を集めています。Pyodideを使用することで、ブラウザ上で直接Pythonコードを実行できるため、インタラクティブな教育コンテンツやデータ分析ツールの開発に使われています。
最近では、LLM(大規模言語モデル)が生成したPythonコードを安全に実行するためのプラットフォームとしても注目されており、CohereのTerrariumなどのツールでもPyodideが採用されています。このような実用的なケースでは、出力処理の安定性が特に重要になってきます。
さて、改行を含むPythonコードを実行しようとすると、以下のようなエラーに遭遇しました:
OSError: [Errno 29] I/O error
この記事では、このエラーが発生する原因を解説し、解決方法を提供します。
問題が発生するケース
以下のようなコードを実行しようとすると、OSErrorが発生します:
// 問題のあるコード例
pyodide.setStdout({
write: (text: string) => outputCapture.capture(text)
});
// 複数行のPythonコードを実行しようとする
const code = `
print("Line 1")
print("Line 2")
print("Line 3")
`;
await pyodide.runPythonAsync(code);
このコードを実行すると、OSError: [Errno 29] I/O error
というエラーメッセージが表示され、プログラムが正常に動作しません。
エラーが発生する技術的背景
このエラーは、Pyodideにおける標準出力(stdout)の処理方法に起因しています。主な問題点は以下の通りです:
- デフォルトの
write
モードでは、出力が断片的に処理される可能性があります。 - 改行を含むテキストが複数のチャンクに分割されて処理されることがあります。
- 出力バッファの管理が適切に行われないことがあります。
これらの要因により、特に複数行のコードを実行する際にエラーが発生しやすくなっています。
解決方法:batchedモードの活用
この問題は、標準出力の設定をbatched
モードに変更することで解決できます:
pyodide.setStdout({
batched: (text: string) => outputCapture.capture(text)
});
batched
モードには以下のような利点があります:
- テキストを一括で処理するため、出力の断片化を防ぎます
- 行単位で完結した出力を受け取ることができます
- 部分的な行や中途半端なテキストフラグメントを避けることができます
実装例:エラーハンドリングを含む完全なコード
以下に、エラーハンドリングやタイムアウト処理を含む完全な実装例を示します:
class OutputCapture {
private output: string[] = [];
capture(text: string) {
this.output.push(text);
}
getOutput(): string {
return this.output.join("\n");
}
clear() {
this.output = [];
}
}
const executePython = async (code: string, timeout: number) => {
try {
if (!pyodide) {
throw new Error("Pyodide not initialized");
}
const outputCapture = new OutputCapture();
// batchedモードで標準出力を設定
pyodide.setStdout({
batched: (text: string) => outputCapture.capture(text)
});
console.error("Executing Python code:", code);
// タイムアウト付きで実行
const resultPromise = pyodide.runPythonAsync(code);
const result = await Promise.race([
resultPromise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Execution timeout")), timeout)
),
]);
return {
content: [
{
type: "text",
text: outputCapture.getOutput()
? outputCapture.getOutput()
: String(result),
},
],
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `Error executing Python code: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
};
このコードには以下のような特徴があります:
- 出力のキャプチャ用クラスの実装
- タイムアウト処理の導入
- エラーハンドリングの実装
-
batched
モードによる安定した出力処理
batchedモードの動作の仕組み
batchedモードは、Pythonコードからの出力を効率的に処理するために設計された特別な動作モードです。このモードでは、テキスト出力の処理に特化した2つの主要なイベントトリガーが存在します:
-
改行文字(newline)の書き込み時
- Pythonコードが改行を含む出力を生成したとき(例:
print()
関数の使用時) - この時点でbatchedハンドラーが呼び出され、改行までの内容が処理されます
- Pythonコードが改行を含む出力を生成したとき(例:
-
明示的なflush操作時
-
sys.stdout.flush()
が呼び出されたとき - バッファ内の内容が即座に処理されます
-
このような仕組みにより、テキスト出力の安定した処理が実現され、エラーの発生を防ぐことができます。特に複数行のコードを実行する場合や、大量の出力を伴うプログラムを実行する際に、その効果を発揮します。
標準出力の設定例:
pyodide.setStdout({
batched: (text: string) => {
// textには完全な行または flush された内容が渡されます
console.log(`Output: ${text}`);
}
});
このような処理フローにより、出力の安定性が確保され、エラーを防ぐことができます。
まとめ
Pyodideで改行を含むコードを実行する際のOSError
は、標準出力の設定をbatched
モードにすることで解決できます。
ちょっとしたことですが、お役に立てば幸いです。