python初心者が、ゲームキャラの口調でパクツイするbotを作ってみた　後編

# 前回までのあらすじ
**本記事は以下の続きになります。**

https://qiita.com/4zna3/items/8cdf3c92bc635ba543e8

前回はTwitterAPIの動作を確認しました。ここからはパクツイしたツイートをこずえちゃんっぽく変換していく機能を実装していきます。



# botの機能を決める（こずえちゃんっぽく文字列を変換）

TLからパクツイするだけならめんどくさいことはしなくていいと思います。しかし遊佐こずえちゃんらしいセリフをしゃべらせるには、以下の要件を満たす必要があります。
　・基本的に平仮名でしゃべる（媒体によっては「ママ」だけは片仮名）
　・演技をしているときなどはちゃんと漢字でしゃべる（漢字でしゃべるって何？）
　・文節の区切りに読点や三点リーダー、長音記号（以下、これらを「区切り文字」とします）が入ることがある
　・敬語は使わない
　・「○○してー？○○しろー……。」みたいな言い回しがある

下2つは対応がめんどくさそうなので今回はやらないことにしましたが、上の3つは必須かなあという感じがします。
自分が今までメインで扱ってきた言語では、3要件ですら実装がめんどくさそうな気がしたので、ここでPythonとやらを触ってみることにしました。
私はこの言語を仕事で扱っていない（そもそもろくに仕事してないのでろくにプログラミングをしていない）のですが、以前少々触ってみたところクソ便利だったと記憶しています。漢字を平仮名に変換したり、文節ごとに区切ったりするモジュールもありそうです。

 # 機能の実装

 ### 確率
遊佐こずえは演技をしている間は漢字交じりでしゃべっていますが、全てのツイートで漢字を使用しているとこずえちゃんらしさが失われてしまいます。「まねをする（=演技をする）bot」とはいえ、さすがにこれは解釈違いです。
よって、わずかな確率で漢字を使用する程度に留めておくことにしました。
また、長音記号を入れるか三点リーダーを入れるか読点を入れるかも確率で決めるのがいいでしょう。

とりあえず、numpyで以下のように確率を設定し、区切り文字を選択するようにしました。（この記事を読んでいる方はほとんど知っている気もしますが、自分の忘備録用として記述しておきます）
[このページ](https://deepage.net/features/numpy-random.html)を参考にしました。


```python:dictionary.py
import numpy as np

# converting_type = 0の時は変換しない。1の時は変換する。
# 以下のように書くと[0]から[n-1]の値が返ってくる
# np.random.choice(n,p=[確率1, 確率2, ... , 確率n])
converting_type = np.random.choice(2, p=[0.03, 0.97])

# 以下のように書くとリストに入った文字列のうちの一つが返ってくる
# np.random.choice(n個の文字列が入ったリスト,p=[確率1, 確率2, ... , 確率n])
delimiter_dic = ['', '…', 'ー', '、', '……', 'ー…', '…………', 'ー……']
delimiter_type = np.random.choice(delimiter_dic, p=[0.2, 0.1, 0.1, 0.2, 0.05, 0.15, 0.05, 0.15])
```

<br>

 ### 文節と文節の間に区切り文字を挿入

漢字を平仮名に変換した後では、文節区切りが困難になることが予想されます。
平仮名に変換を行う前に区切り文字の挿入を行おうと思います。
ざっと調べてみた感じでは、文節ごとに区切ってくれそうなライブラリはなさそうです。
しかし、単語ごとに区切ってくれそうなライブラリは以下のようにいくつか見つけることができました。

<br>

 ### 単語ごとに区切る（いわゆる形態素解析）

+ **[MeCab](http://taku910.github.io/mecab/)**
+ **[Janome](https://mocobeta.github.io/janome/)**
+ **[sudachipy](https://github.com/WorksApplications/SudachiPy/blob/develop/docs/tutorial.md)**

ほかにもいくつかあるようですが、今回は**Sudachipy**を使用することにしました。理由はなんとなくです。
なお、今回は単語ごとではなく文節ごと区切られるのが理想です。今回のようなケースでは、以下の要件を満たすものであればSudachipy以外の何を使用しても問題ありません。

+ 単語ごとに区切ってくれる
+ 単語ごとに品詞を判定してくれる
+ 用言の活用（五段活用、サ行変格活用など）を判定してくれる
+ 用言/助動詞が何活用かを判定してくれる
<br>
<br>

**Sudachipy**をインストール ~~&辞書をリンク~~ します。多彩な語彙力が欲しかったので、私はフルバージョンの辞書をインストールしました。
そんなに語彙力いらんわ！！って人は[公式のGithub](https://github.com/WorksApplications/SudachiPy/blob/develop/docs/tutorial.md)を参考に、語彙力を低下させた辞書をインストールしてください。

```
$ pip3 install sudachipy
$ pip3 install sudachidict_full
# 以下のコマンドは、SudachiPy v0.5.2 以降から利用できなくなりました
$ sudachipy link -t full
```

<br>

【2022年10月16日追記】
**現時点では、[linkコマンド](https://github.com/WorksApplications/SudachiPy/blob/develop/docs/tutorial.md#%E8%BE%9E%E6%9B%B8%E3%81%AE%E7%A8%AE%E9%A1%9E)は使用できないようです。** {sudachipy導入ディレクトリ}/resources/sudachi.json の[Sudachi辞書（systemDict）の設定を変える](https://github.com/WorksApplications/SudachiPy/blob/develop/docs/tutorial.md#%E8%BE%9E%E6%9B%B8%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)（後述）か、プログラム実行時に[使用する辞書を指定する](https://github.com/WorksApplications/SudachiPy/blob/develop/docs/tutorial.md#%E8%BE%9E%E6%9B%B8%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3-python-%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8)方法を取りましょう。

一旦Sudachipyなどのライブラリを使用して単語ごとに区切ったら、以下の法則のもと区切り文字を入れないように工夫し、疑似的に文節ごとに区切ります。
<dl>
<dt>・助詞・助動詞・接尾辞の前には区切り文字は入れない</dt><dd>「ステージに」の「に」や、「大型化」の「化」の前に読点が入るのはおかしいです。</dd>
<dt>・接頭辞の後には区切り文字は入れない</dt><dd>「ぶん殴る」が「ぶん、殴る」になるのは不自然です。</dd>
<dt>・「動詞 + 動詞」「名詞 + 名詞」「用言 + 名詞」は区切らない</dt><dd>「歩き回る」「スカイツリー」「青い空」「吠える犬」は区切りたくないです。用言については、終止形と命令形の以外の場合に区切らないようにすればよさそうです。
「高知県宿毛市愛媛県南宇和郡愛南町篠山小中学校組合立篠山小学校」などは11歳幼女に言わせるような長さの単語ではありませんが、一単語は区切らない原則とします。</dd>
<dt>・句読点をはじめとする補助記号の前後に読点は入れない。</dt><dd>当然です。</dd>
<dt>・句読点をはじめとする補助記号の後に区切り文字は入れない。</dt><dd>こちらも当然です。</dd>
<dt>・その他</dt><dd>「命燃やして恋せよ乙女」はデレステの楽曲名として一つの名詞たりえます。しかしこのような名詞の場合、区切り文字が入るのを避けられないでしょう。正直対応しきれないのである程度まで対応したら諦めます。
とはいえ、さすがに「命、燃やして、恋せよ、乙女」のように区切りが短すぎるのも考え物です…</dd>
ざっと考えただけでもこれだけ対応することがあるので、まだまだ対応が足りてない気しかしません。

考えてばかりでも先に進めないので、とりあえず上記に留意してコードを書くことにしました。案の定ではありますが、この部分だけでも考えることが多すぎたため、今回のプログラムで一番長いのがこの部分になりました。正直他の部分と分離するのもめんどくさい感じになってしまったので、今回は最後にまとめてボン！とコードを置くことにします。




<br>

 ### 英語や漢字・片仮名を平仮名に変換する

文節ごとに区切り文字を入れたら、次は英語を片仮名に変換します。
以下のモジュールを使用しました。

+ **Sudachipy**
+ **[alkana](https://github.com/cod-sushi/alkana.py)**
+ **[pykakasi](https://github.com/miurahr/pykakasi)**

まず、alkanaをインストールします。

```
$ git clone https://github.com/cod-sushi/alkana.py
$ cd alkana.py
$ python3 -m pip install -U .
```

次に、pykakasiをインストールします。

```
$ pip3 install pykakasi
```


素晴らしいことに、英語だろうが漢字だろうが、大方Sudachipyが片仮名に変換してくれます。
しかし、完全に正確に読んでくれるわけではありません。
例えば英語では以下のような感じです。

```
$ sudachipy
twitter
twitter 名詞,固有名詞,一般,*,*,*        ツイッター
EOS

euston
euston  名詞,固有名詞,地名,一般,*,*     ユーストン
EOS

objective
objective       名詞,普通名詞,一般,*,*,*        objective
EOS
```

「twitter」は片仮名に変換できていますが、「objective」は変換できていませんね。
もちろん、スラングなど新しい英単語は日々誕生しているので、英単語の完璧な変換は不可能です。
しかし、少しでも補えた方がいいですよね？
そんなモジュールを探してみたところ……<b>alkana</b>というものがありました。

```
$ alkana twitter
トゥイター

$ alkana euston

$ alkana objective
オブジェクティブ
```

こちらでは「objective」が変換できました。
しかしこちらの場合、「Twitter」は「トゥイター」と変換されています。11歳女児に喋らせるには少々流暢すぎる気もしますね。
また、「Euston」の変換もできていないようです。詳しく調べたわけではないので真偽のほどは不明ですが、地名などの固有名詞に弱いんでしょうか。
基本的にはSudachipyで変換を行い、変換できなかったものについてはalkanaで拾うのがよさそうですね。

文字列を片仮名に変換したら、次は片仮名を平仮名に変換する必要があります。
こちらは<b>[pykakasi](https://pykakasi.readthedocs.io/en/latest/index.html)</b>というモジュールを使用します。
これが大変優れモノでして、片仮名だけでなく漢字も平仮名に変換することができます。
こちらも、<b>alkana</b>同様に、Sudachipyで変換しきれなかった文字列を変換するのにも使えそうです。

しかし、これらのツールを駆使しても、やはり完璧に変換することはできません。pykakasiやalkanaで拾いきれないものについては、Sudachipyのユーザー辞書に追加することで対応を行うこととします。

 ### 補足：Sudachi辞書とユーザー辞書の追加方法

Sudachipyのユーザー辞書はおおまかに以下の手順で追加します。

 ###### 1. Sudachipy.jsonを書き換える
書き換える前に、Sudachipyを使用する端末の中からSudachipy.jsonを探します。
私みたいに何のことかよくわからず複数個所にSudachipyをインストールしてしまった方は、複数のSudachipy.jsonを書き換えるなり不要なSudachipyを削除するなりしてください。
見つかったら、以下のような感じで`"userDict": ["user.dic"]`を追記します。
**【2022年10月16日追記】先述のsystemDictの設定もこちらで追記します。**

```json:Sudachipy.json
{
    "systemDict": "/PATH/TO/system.dic",
    "userDict": ["/PATH/TO/user.dic"],
    "characterDefinitionFile" : "char.def",
    "inputTextPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.DefaultInputTextPlugin" },
        { "class" : "com.worksap.nlp.sudachi.ProlongedSoundMarkPlugin",
          "prolongedSoundMarks": ["ー", "-", "⁓", "〜", "〰"],
          "replacementSymbol": "ー"},
	    { "class": "com.worksap.nlp.sudachi.IgnoreYomiganaPlugin",
          "leftBrackets": ["(", "（"],
          "rightBrackets": [")", "）"],
          "maxYomiganaLength": 4}
    ],
    "oovProviderPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.MeCabOovPlugin",
          "charDef" : "char.def",
          "unkDef" : "unk.def" },
        { "class" : "com.worksap.nlp.sudachi.SimpleOovPlugin",
          "oovPOS" : [ "補助記号", "一般", "*", "*", "*", "*" ],
          "leftId" : 5968,
          "rightId" : 5968,
          "cost" : 3857 }
    ],
    "pathRewritePlugin" : [
        { "class" : "com.worksap.nlp.sudachi.JoinNumericPlugin",
          "enableNormalize" : true },
        { "class" : "com.worksap.nlp.sudachi.JoinKatakanaOovPlugin",
          "oovPOS" : [ "名詞", "普通名詞", "一般", "*", "*", "*" ],
          "minLength" : 3
        }
    ]
}
```

 ###### 2. csvで辞書を作成する

CSVファイルでの辞書の作り方については、[**こちら**](https://github.com/WorksApplications/Sudachi/blob/develop/docs/user_dict.md)に詳しく記されています。
「左連接ID」や「右連接ID」は、多分**[こちら](https://ja.osdn.net/projects/unidic/releases/)**の`unidic-mecab-2.x.x_src.zip`に入っている`left-id.def`や`right-id.def`を参考にすればできると思います。
csvファイルを編集したら保存します。その際、文字コードはUTF-8にします。

 ###### 3. 辞書をビルドする

コマンドプロンプトなどを起動して`$ sudachipy ubuild <手順2で編集した>.csv`とコマンドを打つと、`user.dic`が出力されます。
これを、`Sudachipy.json`と同じディレクトリに入れれば、ユーザー辞書の追加は完了です。




<br>

 ### 敬語をこずえちゃん化

敬語の対義語を何と呼ぶのか知らなかったのですが、どうやら「平語」「常語」などと言ったりするようです。ただ、こずえちゃんの言葉も平語とはちょっと違うような感じもするので、タイトルは「こずえちゃん化」としました。
たとえば「です」を「だよ」に変換するのがいい気はします。しかし、「ですか？」が「だよか？」になってしまうのは問題です。文末の「です」のみ「だよ」に変換するなどの対応が必要です。できそうな気もしますが、一旦ここを改善するのはめんどくさいので保留しておきます。着手次第追記したいと思います。マルコフ連鎖とやらの使用も検討しましたが、個人的にこずえちゃんは模倣の天才と解釈しているので、あえて採用していません。



 # 出来上がったソースコード

大体こんな感じです。
前回作ったツイートのモジュールに、これをいい感じにくっつければ完成です。
自然言語を扱っているだけあって、考えることがやたらと多くなっていますね。
これでもまだ正確な文節区切りや平仮名変換に対応しきれていません。
完璧に変換するのは不可能に近いと思われるので、誤変換を見つけ次第随時改善していくことにしました。
※実際のコードでは、平仮名への変換や区切り文字の挿入以外にも、いろいろな加工をしています。

```python:convertStringToKozue.py
import numpy as np
from sudachipy import tokenizer
from sudachipy import dictionary
import dictionary as myDictionary

morphological_analysis_class = myDictionary.MorphologicalAnalysisClass()
delimiter_class = myDictionary.DelimiterClass()
kana_convert_class = myDictionary.KanaConvertClass()

# ツイートの変換関係の関数を集めたクラス
class ConvertTweetToKozue:

    #単語と単語の間に区切り文字を入れ、ひらがなに変換する関数
    def ConvertKozue(self, string):
        # sudachipyのオブジェクトを作成
        tokenizer_obj = dictionary.Dictionary().create()
        # モードを指定（今回はあまり区切りたくないのでCを指定）
        mode = tokenizer.Tokenizer.SplitMode.C
        # 字句解析されたツイートのオブジェクトを作成
        tokenized_tweet = tokenizer_obj.tokenize(string, mode)

        # 変換した文字列を格納する変数。初期化しておかないと格納時に型が違いますエラーになる
        converted_tweet = None
        for i, m in enumerate(tokenized_tweet):
            delimiter = ""

            # 初回はword_dictを初期化する
            if i ==0:
                word_dict = {}

            # 今のインデックスの単語とその次の単語について、品詞や活用形などを分析する
            word_dict = morphological_analysis_class.FetchWordAndKatsuyou(i, m, tokenized_tweet, word_dict)
            word = word_dict["word"]

            #今のインデックスの単語の「読み」を取得
            reading_form = m.reading_form()

            # 単語をひらがなに変換する
            word = kana_convert_class.KanaConvert(word, reading_form, word_dict["hinshi"])

            # 文字列の連結
            if  converted_tweet is None: #初回はこうしておかないとエラーになる
                    converted_tweet = word
            else:
                    converted_tweet = converted_tweet + word

            # 区切り文字フラグの設定（区切り文字を挿入するか否かを決める）
            insert_delimiter_flag = \
                delimiter_class.SetInsertDelimiterFlag(word_dict)

            # 区切り文字を入れる
            if  insert_delimiter_flag:
                    # ロングフラグの設定（区切り文字に長音記号を使用可能にするか否かを決める）
                    long_flag = delimiter_class.SetLongFlag(word_dict)
                    # カンマフラグの設定（区切り文字にカンマを使用可能にするか否かを決める）
                    comma_flag = delimiter_class.SetCommaFlag(word_dict)
                    # 区切り文字の文字列を決定
                    delimiter = delimiter_class.DecideInsertDelimiter(long_flag, comma_flag)
                    # 区切り文字を挿入
                    converted_tweet = converted_tweet + delimiter
        
        return converted_tweet


    # こずえちゃんっぽいツイートに変換を行うメイン関数
    def Main(self, original_tweet):
        if  original_tweet is not None:
            # 変換するか否かを決める
            #    0:変換しない（3%）SSRと同じ確率っていいよね！
            #    1:ひらがなに変換したり区切り文字を入れたりする
            converting_type_flag = np.random.choice(2, p=[0.03, 0.97])
            
            # 取得したツイートの変換作業
            if converting_type_flag == 1:
                # 変換を行う場合
                # こずえちゃんっぽく変換
                converted_tweet = self.ConvertKozue(original_tweet)

            return converted_tweet
```

```python:dictionary.py
# coding: UTF-8

import re
import alkana
from pykakasi import kakasi
import numpy as np

class MorphologicalAnalysisClass():

    # 単語、活用形など字句解析されたツイートのオブジェクトからを取り出す関数
    #  引数：int（ループ回数）
    #        分析された単語
    #        分析されたツイート 
    #        辞書型
    #  返り値：辞書型
    def FetchWordAndKatsuyou(self, i, m, tokenized_tweet, word_dict):
        # インデックスが0の場合、ツイートの最初の単語を分析する
        if i == 0:
            word = m.surface()
            hinshi = m.part_of_speech()[0]
            katsuyou = m.part_of_speech()[4]
            katsuyou_kei = m.part_of_speech()[5]
        else:
            word = word_dict["next_word"]
            hinshi = word_dict["next_hinshi"]
            katsuyou = word_dict["next_katsuyou"]
            katsuyou_kei = word_dict["next_katsuyou_kei"]

        # インデックスが「i+1」の単語を分析。最後の単語だった場合は空白を挿入
        if i + 1 < len(tokenized_tweet):
            next_word = tokenized_tweet[i + 1].surface()
            next_hinshi = tokenized_tweet[i + 1].part_of_speech()[0]
            next_katsuyou = tokenized_tweet[i + 1].part_of_speech()[4]
            next_katsuyou_kei = tokenized_tweet[i + 1].part_of_speech()[5]
        else:
            next_hinshi = "空白"
            next_word = ""
            next_katsuyou = ""
            next_katsuyou_kei = ""

        word_dict = { \
            "word":word, \
            "hinshi":hinshi, \
            "katsuyou":katsuyou, \
            "katsuyou_kei":katsuyou_kei, \
            "next_word":next_word, \
            "next_hinshi":next_hinshi, \
            "next_katsuyou":next_katsuyou, \
            "next_katsuyou_kei":next_katsuyou_kei, \
            }

        return word_dict


#区切り文字（長音記号、三点リーダー、句読点）関連の関数群
class DelimiterClass():

    # 区切り文字を入れるか否かのフラグを決める関数
    #   返り値
    #       True：区切り文字を挿入する
    #       False：区切り文字を挿入しない
    def SetInsertDelimiterFlag(self, word_dict):
        insert_delimiter_flag = True

        # 記号（句読点、かっこなど）、空白、アルファベット単体の後に区切り文字は入れない。
        if  "記号" in word_dict["hinshi"] or \
            "空白" in word_dict["hinshi"] or \
            re.match(pattern = '[a-zA-Zａ-ｚＡ-Ｚ]', string = word_dict["word"]):
                insert_delimiter_flag = False

        #次の単語がない場合は区切り文字を挿入する
        elif "" == word_dict["next_word"] or "空白" ==word_dict["next_hinshi"]:
            pass

        # 一部の記号は、前にも区切り文字は入れない。
        elif  "記号" in word_dict["next_hinshi"] and \
            re.match(pattern = '[/・:：]', string = word_dict["next_word"]):
                insert_delimiter_flag = False

        # 助詞・助動詞・接尾辞の前には区切り文字は入れない
        elif  "助動詞" in word_dict["next_hinshi"] or\
            "助詞" in word_dict["next_hinshi"] or\
            "接尾辞" in word_dict["next_hinshi"]:
                insert_delimiter_flag = False

        # 接頭辞の後には区切り文字は入れない
        elif  "接頭辞" in word_dict["hinshi"]:
                insert_delimiter_flag = False
        
        # 名詞+代名詞の場合は区切り文字を挿入する
        elif "名詞" == word_dict["hinshi"] and "代名詞" in word_dict["next_hinshi"]:
            pass

        # 用言+名詞の間には区切り文字は入れない
        elif  "動詞" == word_dict["hinshi"] and "名詞" in word_dict["next_hinshi"] or\
            "名詞" in word_dict["hinshi"] and "名詞" in word_dict["next_hinshi"] or\
            "名詞" in word_dict["hinshi"] and "動詞" in word_dict["next_hinshi"] and "サ行変格" in word_dict["next_katsuyou"] or \
            "副詞" in word_dict["hinshi"] and "動詞" in word_dict["next_hinshi"] or\
            "感動詞" in word_dict["hinshi"] and "感動詞" in word_dict["next_hinshi"] or\
            "形容詞" in word_dict["hinshi"] and "名詞" in word_dict["next_hinshi"] or\
            "形容動詞" in word_dict["hinshi"] and "名詞" in word_dict["next_hinshi"]:
                insert_delimiter_flag = False

        # 文節の最後にに来得ない活用形の直後に区切り文字は入れない
        # if  "動詞" == hinshi and "未然形" in word_dict["katsuyou_kei"] or\
        #     "動詞" == hinshi and "連用形" in word_dict["katsuyou_kei"] or\
        #     "動詞" == hinshi and "連体形" in word_dict["katsuyou_kei"] or\
        #     "動詞" == hinshi and "仮定形" in word_dict["katsuyou_kei"]:
        elif  "未然形" in word_dict["katsuyou_kei"] or\
            "連用形" in word_dict["katsuyou_kei"] or\
            "連体形" in word_dict["katsuyou_kei"] or\
            "仮定形" in word_dict["katsuyou_kei"] or\
            "語幹"   in word_dict["katsuyou_kei"] :
                insert_delimiter_flag = False

        return insert_delimiter_flag


    # 区切り文字に読点を含む否かのフラグを決める関数
    #   返り値
    #       True：区切り文字に読点を含む
    #       False：区切り文字に読点を含まない
    def SetCommaFlag(self, word_dict):
        comma_flag = True
        
        # 記号の前後や空白の前、末尾に読点は入れない。
        if  "記号" in word_dict["hinshi"] or\
            "記号" in word_dict["next_hinshi"] or\
            "空白" in word_dict["next_hinshi"] or\
            "" == word_dict["next_word"]:
                comma_flag = False

        return comma_flag



    # 区切り文字に長音記号を含む否かのフラグを決める関数
    #   返り値
    #       True：区切り文字に長音記号を含む
    #       False：区切り文字に長音記号を含まない
    def SetLongFlag(self, word_dict):
        long_flag = True
        
        # 長音記号のうしろに長音記号は入れない。
        if  re.search(pattern = '[-ー~～]$', string = word_dict["word"]):
                long_flag = False

        return long_flag


    # 文節の間に挿入する区切り文字を決める関数
    #  引数
    #    comma_flg: 読点を区切り文字に含むか含まないかのフラグ。
    #        引数の値
    #            long_flag
    #                    True:   長音記号を区切り文字に含む
    #                    False:  長音記号を区切り文字に含まない
    #            comma_flag
    #                    True:   読点を区切り文字に含む
    #                    False:  読点を区切り文字に含まない
    # 返り値
    #    区切り文字の文字列
    #        70%の確率で長音記号
    #        80%の確率で三点リーダー
    #        20%の確率で読点を挿入する
    def DecideInsertDelimiter(self, long_flag, comma_flag):
        delimiter = ""
        # 長音記号を入れる
        if long_flag:
            delimiter_dic = ['', 'ー']
            delimiter = np.random.choice(delimiter_dic, p=[0.4, 0.6])
        # 三点リーダーを入れる
        delimiter_dic = ['', '…', '……', '………', '…………']
        delimiter = delimiter + np.random.choice(delimiter_dic, p=[0.1, 0.45, 0.3, 0.1, 0.05])
        # 読点を入れる
        if comma_flag:
            delimiter_dic = ['', '、']
            delimiter = delimiter + np.random.choice(delimiter_dic, p=[0.7, 0.3])

        return delimiter



# ひらがなに変換を行う関数群
class KanaConvertClass():

    # ひらがなへの変換をする関数
    #   返り値
    #       変換後の文字列
    #       
    def KanaConvert(self, word, reading_form, hinshi):

        henkan_flag = True
        # 読みが分からなかった文字はそのままにしておく
        # ひらがな・カタカナの正規表現のメモ（昔はひらがなやカタカナもそのままにしていた）⇒ァ-ヿｦｰﾝぁｰゔ
        if  "" == reading_form:
                pass

        # ギリシャ文字単体、顔文字によく使われる漢字、だった場合は変換しない
        elif re.search(pattern = '^艸Ͱ-ῼ$', string = word):
                henkan_flag = False

        # アルファベット単体だった場合は変換しない
        elif re.match(pattern = '^[a-zA-Zａ-ｚＡ-Ｚ]$', string = word):
                henkan_flag = False

        # 数字は変換しない
        elif re.search(pattern = '[0-9０-９]', string = word):
                henkan_flag = False
                # 「100万年」を「100万ねん」ではなく「100まんねん」と変換できるようにする。
                # Sudachipyは100万を分割してくれないので、こうするしかない。
                # なお、全部平仮名にしようとすると「いちれいれいまんねん」などと変換する
                word = re.sub(r'十', "じゅう", word)
                word = re.sub(r'百', "ひゃく", word)
                word = re.sub(r'千', "せん", word)
                word = re.sub(r'万', "まん", word)
                word = re.sub(r'億', "おく", word)
                word = re.sub(r'兆', "ちょう", word)
                word = re.sub(r'京', "けい", word)

        # 英語をカタカナ英語に変換
        elif    re.match(pattern = '[a-zA-Zａ-ｚＡ-Ｚ]', string = word):
                    word = reading_form
                    # Sudachipyで変換できなかったときのためにalkanaでも変換する。
                    alkana_converted = alkana.get_kana(word)
                    if alkana_converted is not None and\
                        re.match(pattern = '[a-zA-Zａ-ｚＡ-Ｚ]', string = word):
                        # Sudachipyで変換に成功したときはalkanaはNoneを返す。（alkanaは英語以外の文字列を変換できない）
                        # 読みがNoneになるのはまずいので、alkanaがNone以外を返したときのみ、alkanaの読みを採用。
                        # alkanaが将来的にカタカナを英語に変換する機能を実装したときに備え、アルファベットが入っているかどうかも一応確認する。
                        word = alkana_converted

        # 記号（句読点や絵文字）や空白以外は、Sudachipyの「読み」を採用する
        elif    "記号" not in hinshi and "空白" not in hinshi:
                    word = reading_form
        
        if henkan_flag:
            # pykakasiでひらがなに変換
            mode = kakasi()  #オブジェクトをインスタンス化
            mode.setMode('J','H') #漢字をひらがなに変換
            mode.setMode('K','H') #カタカナをひらがなに変換
            conv = mode.getConverter()
            word = conv.do(word)

        return word
```

<br>
<br>

 ### 最後に

ここまで長々と変換について語ってきましたが、これ以外にも様々な対応を行っています。しかし、やはり未だに十分な対応ができているとは言えません。誤変換への対応は、開発開始から3か月が経とうとしている現在も続いています。完璧に変換できるようにしようとは思いませんが、まだまだ改善の余地はありそうです。

また、こういうのを書くのは初めてだったので、大変つたない記事になってしまった点は反省です。次はもっと情報をまとめられるようにしたいと思います。Pythonについては付け焼き刃の知識しかないので、基礎から勉強しなおそうと思います。

**フォローしてね！↓**

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ぜったいー……、さむいー……</p>&mdash; TLのまねをする遊佐こずえ (@Manesuru_Kozue) <a href="https://twitter.com/Manesuru_Kozue/status/1390547954553872385?ref_src=twsrc%5Etfw">May 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>