深夜2時のトラブルとinput()関数が招いた結末
深夜2時ちょうど、私の電話が激しく震えました。本番環境で動いていた重要な自動化スクリプトが突然「フリーズ」し、後続の一連のタスクが遅延してしまったのです。15分ほどログを調査した結果、非常に初歩的な原因が判明しました。新人の同僚が、設定パラメータの入力に標準の input() 関数を使用していたのです。
ターミナルでの実行中、担当者が数量の入力欄に誤って無効な文字を入力してしまいました。エラーを表示して再入力を促す代わりに、スクリプトは ValueError でそのままクラッシュ。当時のインターフェースはただの白いテキストの羅列で、ガイドもメニューもなく、オペレーターは次に何をすべきか勘に頼るしかない状態でした。
その瞬間、私は悟りました。デフォルトの入力関数に頼り続けていれば、いずれ人間のタイポ(入力ミス)によってシステムが崩壊することを。
なぜ従来のinput()はトラブルを招きやすいのか?
自分一人で使うスクリプトであれば、input() でも十分かもしれません。しかし、ツールを他人に提供したり、実際のサーバーで実行したりする場合、以下の3つの致命的な弱点が露呈します。
- 制御能力(Validation)の欠如: ユーザーがメールアドレスや整数の形式を正しく入力したかを確認するだけで、何十行もの
if-elseを書く必要があります。私の経験上、手動のバリデーションコードはCLIスクリプト의ロジックの40%を占めることもあります。 - 貧弱なユーザーエクスペリエンス(UX): 矢印キーでの選択も、チェックボックスもありません。スペース一つ間違えるだけで、すべてが台無しになります。
- スパゲッティコード化: 質問が増えるほど、コードは無限にネストされた
while Trueループの塊のようになっていきます。
解決策:手動対応から専用ライブラリへ
当初、私はラッパー関数を書いて急場をしのごうとしました。例えばこのような形です:
def get_integer_input(prompt):
while True:
try:
return int(input(prompt))
# エラー発生時の処理
except ValueError:
print("エラー!整数を入力してください。")
この方法でクラッシュは防げますが、見た目はまだ非常に素人っぽいです。argparse や click も試しましたが、これらは引数(フラグ)の取得には強力なものの、ステップバイステップの対話的なやり取りには柔軟性が欠けていました。
最終的に行き着いたのが Questionary です。これは prompt_toolkit をベースに構築されたライブラリで、味気ない Python スクリプトを、スムーズなメニューやチェックボックスを備えた本格的なCLIツールへと変貌させてくれます。
Questionary — モダンなCLIの救世主
インストールはコマンド一行で完了します:
pip install questionary
以下は、古臭い input() 関数を置き換えるために私がよく使う3つの方法です。
1. 選択メニューの作成 (Select)
ユーザーに “1” や “2”、あるいは “Yes/No” を入力させる代わりに、矢印キーで選択させましょう。これにより、タイポを完全に排除できます。
import questionary
action = questionary.select(
"実行する操作を選択してください:",
choices=[
"サーバーの状態を確認",
"データのバックアップ",
"サービスの再起動",
"終了"
]
).ask()
print(f"実行中: {action}")
2. 複数項目の同時選択 (Checkbox)
複数のオプションを設定する必要がある場合に非常に便利です。ユーザーはスペースキーでチェックを入れ、Enterキーで確定するだけです。
features = questionary.checkbox(
"インストールするモジュールを選択してください:",
choices=["Nginx", "PostgreSQL", "Redis", "Docker"]
).ask()
print(f"以下のモジュールをインストールします: {', '.join(features)}")
3. 即時のデータバリデーション (Validation)
これが最も「価値のある」機能です。Questionary を使うと、ユーザーが入力している最中にデータを検証できます。形式が正しくない場合、Enterキーを押すことができず、即座にエラーメッセージが表示されます。
import re
def validate_ip(text):
# IPアドレスの形式をチェックする正規表現
pattern = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
if re.match(pattern, text):
return True
return "IPアドレスが無効です!(例:192.168.1.1)"
ip_address = questionary.text(
"対象サーバーのIPを入力してください:",
validate=validate_ip
).ask()
役立つヒント:複雑な正規表現を素早くテストしたいときは、コードに組み込む前に toolcraft.app の Regex Tester を使ってロジックを確認するのがおすすめです。
実践的な応用:サーバー管理スクリプト
Questionary を実際の管理スクリプトに組み込んだ時の威力を見てみましょう。プロフェッショナルに見えるよう、少し色を加えてみます。
import questionary
from prompt_toolkit.styles import Style
# カスタムスタイルの定義
custom_style = Style([
('qmark', 'fg:#673ab7 bold'),
('question', 'bold'),
('answer', 'fg:#f44336 bold'),
('pointer', 'fg:#673ab7 bold'),
('highlighted', 'fg:#673ab7 bold'),
('selected', 'fg:#cc5454'),
])
def main():
print("--- 管理システム V1.0 ---")
env = questionary.select(
"環境を選択してください:",
choices=["Staging", "Production", "Local"],
style=custom_style
).ask()
if env == "Production":
sure = questionary.confirm(
"警告:本番環境(Production)を操作しようとしています。続行しますか?",
default=False
).ask()
if not sure: return
service_name = questionary.text(
"サービス名(例:nginx):",
validate=lambda text: True if len(text) > 0 else "空欄にすることはできません!"
).ask()
task = questionary.select(
f"{service_name} に対する操作:",
choices=["Restart", "Stop", "Status"]
).ask()
print(f"\n[OK] {env} 上の {service_name} に対して {task} を実行中...")
if __name__ == "__main__":
main()
エンジニアの視点から
Questionary に切り替えてから、くだらない入力エラーのために深夜に叩き起こされることはなくなりました。運用チームも、CLIのインターフェースが本物のソフトウェアのように使いやすくなったと喜んでいます。
教訓はこうです:ユーザーが入力するデータを決して100%信用してはいけません。メニューで選択肢を制限し、可能な限りすべてをバリデーションしてください。Questionary は単にコードを綺麗にするだけでなく、システムのための強固な保険となります。
自動化ツールや社内ツールを構築しているなら、今すぐ Questionary を試してみてください。その効果にきっと驚くはずです。

