僕のpythonのエラーはほぼ全てVOICEVOX経由で喋るのだ🫛

# ネタだと思うだろう？マジだ。
pythonを書いていて、エラーが起った時に声で知らせてほしいと思うことは「よくある」のではないかと僕は思います。

```
Traceback (most recent call last):                                                                
  File "/home/hoge/example.py", line 4, in <module>                                     
    with open('zunda.txt') as fp:                                                                
FileNotFoundError: [Errno 2] No such file or directory: 'zunda.txt' 
```
このようなエラーを静かに吐いて、猫のように人知れず死んでしまうのはよくないです🐈
これが例えば20分とかかかるような処理だったりすると、時々生存確認をせねばなりません。超面倒です。

では、ずんだもんが下記のように報告してくれたらどうでしょう？

```
ずんだもん「zunda.txtなんてファイルはないのだ🫛」
```

確認の手間がなくなり、早く気づけ、エラーの内容を日本語で言ってくれて、あまりに仕事が捗る、そう思いませんか？僕はマジで真顔です。決してずんだもんの愛くるしさに心乱れたためだけではないのです。

だから、そんなpythonのスクリプトを書きました。
以前書いたninvoicevoxというパッケージに統合しています。

https://github.com/uesseu/ninvoicevox

使い方はこうです。
```
from ninvoicevox import zundaerror
```
たどし、初回のみこうすることを推奨します。

```
from ninvoicevox import zundaerror
zundaerror.install()
```

これでずんだもんがほとんどのエラーを報告します。

# どういうこと？
VOICEVOXを使えば声は簡単に出せますね。ただし、localhostでhttpサーバーを立てないといけません。

https://github.com/VOICEVOX/voicevox_engine/releases

そんで、サーバーの出力をゴニョゴニョするのですが、割と面倒です。だから、そいつが出力したものをpythonやshellで扱うクライアントを以前書いたことがあります。名前はninvoicevoxといいます。全然たいしたコードではないです。こんなクライアントは簡単に書けるから、誰でも適当に書くといいと思います。

https://qiita.com/uesseu/items/cb6b128e352228ea217d

https://github.com/uesseu/ninvoicevox

というわけで、pythonのエラーをずんだもんエラーにします。

# カスタムExceptionを報告するのだ🫛
これがninvoicevoxを使った場合の例です。多重継承を使っていますので、継承の順番には気を使う必要があります。また、__str__を弄っているので少々乱暴なやりかたです。

```python
from ninvoicevox import AsyncQueue, Speaker, get_speaker_info

class ZundamonSays:
    def __str__(self):
        error_txt = ''.join(self.args)
        info = get_speaker_info().name['ずんだもん']['ノーマル']
        Speaker(info, enable_cache=True).text(error_txt).speak()
        return error_txt

class ZundamonError(ZundamonSays, Exception):
    pass

raise ZundamonError('エラーなのだ。')
```

- ZundamonSaysクラスで__str__メソッドを書き換える
- ZundamonSaysクラスを先、Exceptionクラスを後で多重継承
- raiseする

うわぁ、簡単なのだ。
これでずんだもんがカスタムエラーを報告してくれるのだ🫛

# 既存のExceptionもずんだもんが言うのだ🫛
自分でカスタマイズしたExceptionは上記のとおり出来るのだ。ただ、既存のものはまだ出来ないのだ。なので、既存のExceptionをキャッチしてずんだもんが読みあげるのだ。pythonなら```sys.excepthook```を書きかえればいいのだ🫛
見たまんまだけれど、この```sys.excepthook```はエラーが起った時のフックなのだ🫛

```python
import sys
import traceback


def zundamon_says(args: str):
    error_txt = ''.join(args)
    Speaker(get_speaker_info().name['ずんだもん']['ノーマル'],
            enable_cache=True).text(error_txt).speak()
    return error_txt

def _exception_hook(cls, er, tb):
    if cls is FileNotFoundError:
        zundamon_says(
            f'{er.filename}というファイルやフォルダはないのだ。'
        )
    traceback.print_exception(cls, er, tb)

sys.exceptionhook = _exception_hook
```

これでFileNotFoundErrorがraiseする前にずんだもんがそれを読みあげるのだ。このif文をふやして網羅すればいいのだ。書いた関数は下記なのだ🫛

<details><summary>超長く醜いコード</summary>


```python
import traceback
import sys
import os


def zundamon_says(args: str):
    error_txt = ''.join(args)
    Speaker(get_speaker_info().name['ずんだもん']['ノーマル'],
            enable_cache=True).text(error_txt).speak()
    return error_txt

def _exception_hook(cls, er, tb):
    if cls is FileNotFoundError:
        zundamon_says(
            f'{er.filename}というファイルやフォルダはないのだ。'
        )
    elif cls is FileExistsError:
        zundamon_says(
            f'{er.filename}というファイルやフォルダが既にあるのだ。'
        )
    elif cls is IsADirectoryError:
        zundamon_says(f'{er.filename}はディレクトリなのだ。')
    elif cls is NotADirectoryError:
        zundamon_says(
            f'{er.filename}はディレクトリではないのだ。'
        )
    elif cls is PermissionError:
        zundamon_says(
            f'{er.filename}に対して十分なアクセス権がないのだ。'
        )
    elif cls is ProcessLookupError:
        zundamon_says('プロセスが見付からないのだ。')
    elif cls is TimeoutError:
        zundamon_says('タイムアウトなのだ。')
    elif cls is InterruptedError:
        zundamon_says('中断するように言われたのだ。')
    elif cls is ConnectionRefusedError:
        zundamon_says('接続が拒否されたのだ。')
    elif cls is ConnectionAbortedError:
        zundamon_says('接続が中断されたのだ。')
    elif cls is BrokenPipeError:
        zundamon_says('パイプが壊れたのだ。')
    elif cls is ChildProcessError:
        zundamon_says('子プロセスが失敗したのだ。')
    elif cls is BlockingIOError:
        zundamon_says('非同期処理に失敗したのだ。')
    elif cls is UnicodeEncodeError:
        zundamon_says('ユニコードのエンコードに失敗したのだ。')
    elif cls is UnicodeDecodeError:
        zundamon_says('ユニコードのデコードに失敗したのだ。')
    elif cls is UnboundLocalError:
        zundamon_says('ローカル変数が変なのだ。')
    elif cls is TypeError:
        zundamon_says('型のエラーなのだ。')
    elif cls is NameError:
        zundamon_says(f'{er.name}は定義されていないのだ。')
    elif cls is ZeroDivisionError:
        zundamon_says('ゼロで割り算をしてはいけないのだ。')
    elif cls is OverflowError:
        zundamon_says('オーバーフローなのだ。')
    elif cls is ArithmeticError:
        zundamon_says('数学的に間違いなのだ。')
    elif cls is AssertionError:
        zundamon_says('テストが失敗したようなのだ。')
    elif cls is AttributeError:
        zundamon_says('属性のエラーなのだ。')
    elif cls is EOFError:
        zundamon_says('文字が入力されなかったのだ。')
    elif cls is ImportError:
        zundamon_says(f'{er.name}をインポートできなかったのだ。')
    elif cls is ModuleNotFoundError:
        zundamon_says(f'{er.name}をインポートできなかったのだ。')
    elif cls is IndexError:
        zundamon_says('添字が範囲外なのだ。')
    elif cls is KeyError:
        zundamon_says('辞書のキーが違うのだ。')
    elif cls is KeyboardInterrupt:
        zundamon_says(
            'ユーザーさんが中止しろって言ったからやめたのだ。'
        )
    elif cls is MemoryError:
        zundamon_says('メモリー不足なのだ。')
    elif cls is BufferError:
        zundamon_says('バッファ関連のエラーなのだ。')
    elif cls is LookupError:
        zundamon_says('キーが違うのだ。')
    elif cls is OSError:
        zundamon_says('OS関連のエラーなのだ。')
    elif cls is RecursionError:
        zundamon_says('再帰的なエラーなのだ。')
    elif cls is ReferenceError:
        zundamon_says('既にガベコレされているのだ。')
    elif cls is RuntimeError:
        zundamon_says('何かよくわからないエラーなのだ。')
    elif cls is IndentationError:
        zundamon_says('インデントがおかしいのだ。')
    elif cls is TabError:
        zundamon_says('タブとスペースどっちかにすべきなのだ。')
    elif cls is SystemError:
        zundamon_says('インタプリタのエラーなのだ。')
    elif cls is SyntaxError:
        zundamon_says('文法が間違っているのだ。')
    elif isinstance(er, ZundamonSays):
        pass
    else:
        zundamon_says('何らかのエラーなのだ。')
    traceback.print_exception(cls, er, tb)


sys.excepthook = _exception_hook
```

</details>

# 結果
- pythonがエラーを吐くときにずんだもんが教えてくれる
- pythonの作業が捗るようになるかはこれから試す
- 今後はpythonのお叱りで一々心が潤うようになるであろう
- キャッシュを効かせることができるのでVOICEVOXの起動は初回のみ

以上です。