$ python3 -V
Python 3.6.4
$ pip3 show pyknp
Name: pyknp
Version: 0.4.1
$ juman -v
juman 7.01
引用符が消える
以下のようなPythonスクリプトで分かち書きを行なった。
from pyknp import Juman
t = Juman(jumanpp=False)
text = "彼は\"Hello!\"と言った。"
print("Input : {}".format(text))
tokens = [token.midasi for token in t.analysis(text).mrph_list()]
print("Tokenized : {}".format(tokens))
$ python3 tokenizer.py
Input : 彼は"Hello!"と言った。
Tokenized : ['彼', 'は', 'Hello!', 'と', '言った', '。']
よく見るとHello!を囲っていた引用符(")がなくなっている。
レアな現象ではあるもの、オフセットがずれる原因になっていた。
一時的な対策
アポストロフィーなら消えないようなので置換する。
text = "彼は\"Hello!\"と言った。".replace("\"","'")
$ python3 tokenizer.py
Input : 彼は'Hello!'と言った。
Tokenized : ['彼', 'は', "'Hello!'", 'と', '言った', '。']
引用符にこだわりがないならこれで良い。
コードから原因を推測して直す。
pyknpの中にあるpyknp/juman/morpheme.pyにおいて引用符が閉じている場合、引用符を意図的に消していることがわかった。
これは、コマンドで実行したjumanの解析結果のパースに絡んだ処理のよう。
コマンドでjumanを実行すると以下のようになる。
$ juman
彼は"Hello!"と言った。
彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ 漢字読み:訓 カテゴリ:人"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
"Hello!" "Hello!" "Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
言った いった 言う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:言う/いう 補文ト"
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS
代表表記の部分を見ると引用符で囲まれているのがわかる。
引用符内は空白が使用可能であるため、パースをする際引用符で囲まれた範囲内の空白を無視する必要がある。
そんなわけで引用符で囲まれた範囲を抽出可能なよう設計してあり、また抽出後に引用符は取り除かれるようになっている。
そのため、見出し語の引用符も消されてしまっている。
pyknp/juman/morpheme.pyの一部のif文を次のように直す。
if part != "" and char == ' ' and not inside_quotes:
#if part.startswith('"') and part.endswith('"') and len(part) > 1:
if part.startswith('"') and part.endswith('"') and len(part) > 1 and len(parts) > 2:
parts.append(part[1:-1])
else:
parts.append(part)
これで"Hello!" "Hello!" "Hello!"の部分の引用符は消されなくなった。
$ python3 tokenizer.py
Input : 彼は"Hello!"と言った。
Tokenized : ['彼', 'は', '"Hello!"', 'と', '言った', '。']
治った。やったね。
ちなみに引用符がらみで他の問題もある。
$ python3 tokenizer.py
Input : 彼は" Hello!" と言った。
Tokenized : ['彼', 'は', '"', '\\ ', 'Hello!" Hello!"', '\\ ', 'と', '言った', '。']
Hello! Hello!とテンション高い人みたいになってしまった。
コマンドでjumanを実行した場合はこう。
juman
彼は" Hello!" と言った。
彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ 漢字読み:訓 カテゴリ:人"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
" " " 未定義語 15 その他 1 * 0 * 0 NIL
\ \ 特殊 1 空白 6 * 0 * 0 NIL
Hello!" Hello!" Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
\ \ 特殊 1 空白 6 * 0 * 0 NIL
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
言った いった 言う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:言う/いう 補文ト"
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS
お分りいただけただろうか。
Hello!" Hello!" Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
引用符が来ると次の引用符まで問答無用でまとめられるので、
Hello!" Hello!"
Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
にしかパースされなくなってしまう。
だからpyknpでの解析結果がおかしくなっている。
これは以下のようにpyknp/juman/morpheme.pyの1行を修正する。
#if inside_quotes and char == ' ' and part == '"':
if inside_quotes and char == ' ' and part[-1] == '"':
inside_quotes = False
今までは引用符単体の次に空白がこないと機能しなかったが、引用符の次に空白がこれば良いようにした。
$ python3 tokenizer.py
Input : 彼は" Hello!" と言った。
Tokenized : ['彼', 'は', '"', '\\ ', 'Hello!"', '\\ ', 'と', '言った', '。']
治った。やったね。
githubに置いておく。
取り急ぎk141303/juman_temporarilyにJumanを継承する形で書いた修正コードを挙げておきます。
あとで本家にプルリクしたいと思います。
=> お粗末ですが修正させていただきプルリクParse bug fixes for Juman analysis results. しました。
以上。