0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyodideでPythonのinput()を実装してみた【SmartMemo開発記録⑫】

0
Last updated at Posted at 2026-09-13

はじめに

現在、個人開発している「SmartMemo」に、Pythonコードを実行できる機能を追加しています。SmartMemoは、Markdownでメモを書きながら、その中にPythonコードを記述して実行できるアプリです。

これまでは、

print("Hello,World!")

のようなコードを実行し、実行結果をMarkdownの中に自動で記録するところまで実装していました。しかし、Pythonを実行できるようになると、次の欲しくなったのが、

name = input("名前を入力してください:")
print("こんにちは",name)

のようなinput()による入力機能です。今回は、このinput()対応を実装しました。

今回実装したこと

今回の実装では、Pythonの

input()

をSmartMemo上の入力フォームに置き換える仕組みを作りました。

最終的には、

Pythonコード
   ↓
input()を検出
   ↓
web Workerから入力要求
   ↓
SmartMemoに入力欄を表示
   ↓
ユーザーが入力
   ↓
Workerへ入力値を送信
     ↓
Pythonの処理を再開
     ↓
実行結果をMarkdownへ記録

という流れになっています。

PyodideをWeb Workerで実行

SmartMemoでは、Pythonコードの実行にPyodideを使用しています。また、Pythonの実行によってブラウザの画面が固まらないように、PyodideはWeb Worker上で動かしています。

そのため、Pythonコードは、

pyodide.runPythonAsync(convertedCode);

によってWorker内で実行しています。ここでは問題になったのがinput()でした。

input()をそのまま実行するとエラーになる

最初は普通にPythonの

name = input("名前:")

を実行すればよいと思っていました。
ところが、Web Worker内では通常のブラウザUIを直接操作ができません。
実際に試してみると、

ReferenceError: prompt is not defined

などのエラーが発生しました。さらにPyodide側では、

OSError:[Errno 29]I/O error

のようなエラーも発生しました。
ここで、「Pythonのinput()をそのまま使うのが難しい」ということが分かりました。

setStdin()も試してみた

次にPyodideの標準入力機能であるsetStdin()を使う方法も試しました。

しかし、今回の構成では、

stdin

からWebページ上の入力フォームに繋げる必要があります。ユーザーが入力するまで待つ必要があるため、非同期処理との組み合わせが問題になりました。

setStdin()のコールバックからPromiseを返して待機する方法も試しましたが、上手くいきませんでした。そこで、Pythonの標準入力そのものを使うのではなく、SmartMemo独自の入力処理を作る方向に変更しました。

smart_input()を作る

そこでWorker側に、

async function smartInput(prompt){

    self.postMessage({
        type: "input_request",
        prompt: prompt
    });

    return new Promise(function(resolve){
        inputResolver = resolve;
    });
}

という関数を作りました。

この関数が呼ばれると、

Worker
 ↓
input_request
 ↓
メインスレッド

という形で入力要求を送ります。

SmartMemo側で入力フォームを表示

input_requestを受け取ったJavaScript側では、入力フォームを動的に生成します。
スクリーンショット 2026-09-13 135245.png
ユーザーが「入力」を押すと、

pyodideWorker.postMessage({
    type:'input',
    userInput: userInput
});

として、待機していたPromiseを解決します。これによって、Python側の処理が再開されます。

input()をsmart_input()へ交換

もう一つ問題になったのが、

input("名前:")

をどうやってSmartMemo独自の処理に繋げるかです。そこで、Pythonコードを実行する前にJavaScriptでinput()を検出し、

const convertedCode = code.replace(
    /input\(\s*(["'])(.*?)\1\s*\)/g,
    'await smart_input("$2")'
);

という形で交換することにしました。例えば、

name = input("名前:")
print("こんにちは", name)

が

name = await smart_input("名前:")
print("こんにちは", name)

という形になります。PyodideのrunPythonAsync()ではトップレベルのawaitを利用できるため、この方法で入力待ちを実現しました。

複数のinput()にも対応

最初は1回の入力だけを考えていましたが、実際には、

name = input("名前:")
age = input("年齢:")

print("名前:", name)
print("年齢:", age)

のようなコードも実行したくなります。そこで、入力が1回終わったら次のinput()へ進む仕組みにしました。

名前:
[入力欄]

  ↓

年齢:
[入力欄]

  ↓

実行結果

という流れで複数の入力を処理できるようになりました。

int(input())にも対応

さらに、

age = int(input("年齢:"))

のように、input()で受け取った値をint()で数値に変換するコードにも対応しています。
input()で受け取った入力値は、Pythonでは文字列(str)として扱われます。
そのため、数字として計算などに利用したい場合は、int()を使って整数に変換します。

実際に、

num = int(input("数字を入力してください:"))
print("入力値:", num)

でも正常に動作することを確認しました。

実装中に起きた問題

今回、一番苦労したのが入力UIと実行結果の扱いでした。
最初はPythonの出力を、

outputDiv.textContent += text;

のように親要素へ直接追加していました。しかし、入力フォームも同じoutputDivの中に入っているため、textContentを書き換えると、入力フォームそのものが消えてしまう問題が起きました。問題を解決するため、出力部分を分離しました。

outputDiv
├── outputTextDiv
└── inputDiv

Pythonのprint()は、

[data-smart-output-text]

だけに追加するようにし、これによってPythonの出力と入力UIを分離できました。

Stop→Runで入力欄が二重になる問題

もう一つ問題になったのが、

Run
 ↓
input()
 ↓
Stop
 ↓
Run

という操作で、Run押してからStopを押して、再度Runすると入力欄が2つ表示される、問題がありました。
スクリーンショット 2026-09-12 141204.png
そこでStopの処理の中で、

const inputDiv =
    outputDiv.querySelector('[data-smart-input]');

if(inputDiv){
    inputDiv.remove();
}

として、残っている入力UIを削除するようにしました。
これで、

Stop
 ↓
入力UI削除
 ↓
Worker再作成
 ↓
Run
 ↓
入力UIを1つだけ表示

という動作になりました。

最終的にできるようになったこと

  • input()による入力
  • 複数のinput()
  • int(input())
  • print()出力
  • Python実行エラー
  • エラー後の再実行
  • Stop → Run
  • 入力UIの重複防止

まとめ

今回のinput()は、SmartMemoではPyodideをWeb Worker上で動かしているため、通常のinput()処理ができないので、PythonからJavaScriptへ入力要求を送り、ブラウザ上の入力フォームでユーザーの入力を受け取り、その値を再びWeb Workerへ渡しPythonの処理を再開する仕組みを実装しました。

処理は以下のものになります。

```text
Python
  ↓
Pyodide
  ↓
Web Worker
  ↓
JavaScript
  ↓
HTML入力フォーム
  ↓
Web Worker
  ↓
Pyodide
  ↓
Python

おわりに

個人開発では、完璧に作るよりは、動かしてみる。エラーになる。原因を調べる。修正する。動かしてみる。の繰り返しが大事なのだと思いました。今後はさらに、Python実行で、グラフ表示なども挑戦していきたいと思っています。来週からは、SQLiteからPostgreSQLに移行後 → Multi-languageへ進もうと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?