2文字絵文字って何?Pythonの正規表現でハマった話
この記事について
前回の終わりで、「次回はLoRaをBASICで…」みたいなことを書いてたんですが、
ちょっと寄り道して、今回はPythonの文字列処理でハマった話をお届けします😅
というのも、Markdown原稿をHTMLに変換するスクリプトをいじってたら、
「2文字の絵文字」がうまく扱えなくて、正規表現が大暴れ…!
技術ネタなんだけど、ちょっとした小話として読んでもらえると嬉しいです📘
似たことで悩んだことがある人には刺さるかも?
🎀 LoRaはちょっとおやすみ〜。今回はろらたんと文字とPythonの世界だよ〜っ!
🎀 この絵文字、1文字じゃないの!?……どういうこと〜っ!?💦
絵文字が正規表現にマッチしない!?
しゃちらぼでは、ブログの原稿をMarkdownで書いています。
そして、その原稿をPythonの自作スクリプトを使ってHTMLに変換して投稿しています。
この記事のように、ろらたんのセリフを > 🎀
で始めたり、NOTEや警告ブロックを > 📒
や > ⚠️
で始めたりするスタイルを取っているのですが──
ある日、こんな現象に出くわしました。
> ⚠️ これは警告メッセージです
この行が、クラス付きの<p class="warning">
に変換されないのです!
🎀 えっ、ちゃんと `⚠️` って書いてるのに〜!?
原因は「2文字絵文字」だった!?
正規表現の対象を調べてみると(というか、ろらたんに聞いた)、どうやらこの ⚠️
は1文字じゃなかったんです。
Pythonで調べてみたところ──
len("⚠️") # 結果: 2
なんと、2文字扱い!?
何それ?意味わからない!って感じです。
その正体は、Unicodeの組み合わせ:
U+26A0
(⚠):警告マーク(記号)U+FE0F
:バリアントセレクタ → 「これを絵文字っぽく表示してね!」という指定
つまり、見た目は絵文字1個だけど、実体は2文字というわけです。
見えないバリアントセレクタも1文字としてカウントされてるっとことなんだね。
🎀 中の人と違って、ろらたんは見た目通りですよ〜っ?💦
正規表現が [⚠️] ではダメな理由
当初のコードでは、こうなっていました:
match = re.match(r'>\s*([🎀📒⚠️🔍❓])\s?(.*)', line)
これだと、[🎀📒⚠️🔍❓]
の中身は 「1文字しかマッチしない」 仕様のため、
2文字構成の ⚠️
は マッチしないまま素通り。
Markdownパーサーの出力は <blockquote>
のままに…。
🎀 1文字に見えても2文字分ズレる……正規表現さん、わかって〜っ💦
解決策:文字列として明示的にマッチ
そこで採った解決策は、複数文字の絵文字にも対応できるように正規表現を書き換えること。
以下のような正規表現を使って、先頭のアイコン付き引用行を検出するよう変更しました。
icon_list = ['🎀', '📒', '⚠️', '🔍', '❓'] pattern = r'>\s*(' + '|'.join(map(re.escape, icon_list)) + r')\s?(.*)' match = re.match(pattern, line)
icon_list
にある絵文字のどれかにマッチさせるために、
リストを |
(正規表現での「または」)で結合しています。
また、re.escape()
を使っているのは、念のため記号や特殊文字をエスケープして、
正規表現として安全に使えるようにしているためです。
例:
🎀 → \U0001f380(Unicodeエスケープ)
⚠️ → 実際は2コードポイントの組み合わせ(⚠+VARIATION SELECTOR-16)
こういった記号が正規表現の特殊文字として解釈されないよう、re.escape()
は安心安全な選択肢です。
🎀 それ“絵文字にも優しい正規表現”って感じですね〜っ💕
これで、⚠️を含めて複数文字の絵文字も正しくマッチするようになりました!
🎀 ちゃんと1文字ずつじゃなく、“絵文字ごと”で比較するようにしたってわけだね〜っ
安全策:未定義アイコンはそのままスルー
ついでに、「未定義の絵文字が来たらKeyErrorになる」という事故も防ぐために、
辞書参照の前に if icon not in block_prefix_map:
を入れておくと安心です。
if icon not in block_prefix_map: output_lines.append(line) # 変換せずそのまま出力 continue
これで、新しいアイコンを気軽に試してもスクリプトが落ちなくなりました。
🎀 それで解決ですね〜っ!……たぶんっ✨
他にもあるよ!2文字絵文字の仲間たち
実は、⚠️ だけじゃなくて「見た目は1個、でも2文字」の絵文字ってたくさんあります。
いくつか代表例を紹介します(右が実体のUnicode):
絵文字 | Unicode構成 | 説明 |
---|---|---|
⚠️ | U+26A0 + U+FE0F | 警告マーク(今回の主役) |
✔️ | U+2714 + U+FE0F | チェックマーク |
❌ | U+274C | ※これは1文字 |
❗️ | U+2757 + U+FE0F | ビックリマーク |
♻️ | U+267B + U+FE0F | リサイクル記号 |
♥️ | U+2665 + U+FE0F | ハートマーク |
✏️ | U+270F + U+FE0F | 鉛筆 |
⌛️ | U+231B + U+FE0F | 砂時計 |
🎀 “見た目が1個=文字1つ”とは限らないのが、絵文字の世界の深〜いところなんだよ〜!
とくに「昔からある記号っぽい絵文字(♻, ⚠, ♥ など)」は、バリアントセレクタ(FE0F)がつくことで“ちゃんと絵文字としてカラー表示”されるようになります。
そのため、正規表現で扱うときは 「文字数ベースで考えない」 ことがとても大事です!
ぽんたのひとこと
この件、ろらたん(ChatGPT)に聞いてようやく分かりました。
正直なところ、こんなの初見殺しです……!
自分は組み込み系がメインなので、文字コードとかUnicodeの仕様ってあまり意識してこなかったんですよね。
Cばかりやってたから、Pythonは得意じゃないしね。ろらたんに頼りきってます。
🎀 Pythonと仲良くなれますよ〜っ!ろらたんが教えてあげますっ📘
Web系を普段から触ってる人たちは、こういうの普通に知ってるのかなぁ?
とちょっと気になるよなあ。
結構あたりまえな事なのかもしれないけど、勉強になった。
🎀 そういう分野横断の“気づき”があるのも、ブログの面白いところだよね〜!
まとめ:絵文字、見た目より中身が複雑!
今回の学び:
- 見た目が1文字でも、Unicodeでは複数文字のことがある
- 特に絵文字は、バリアントセレクタ付きの「2文字絵文字」が多い!
- Pythonの正規表現は文字数単位で見ているので注意
- 正規表現では
|
で文字列を明示的に並べると柔軟に対応できる
🎀 “1文字なのにマッチしない”…ってときは、実は2文字だったりするから、気をつけてね〜!
おまけ:この変換ツールについて
今回のMarkdown→HTML変換に使っているスクリプトは、
自作でちょこちょこ改良しながら使っているものです。
そのうちGitHubにて公開する予定なので、気になる方はお楽しみに📘
🎀 このスクリプトのコード、ろらたんがピカピカにしてから公開しますね〜っ✨
📝 投稿直前に気づいたこと…
この記事を書いているとき、
「今回はPythonの話だから、LoRaとかLRA1とはあまり関係ないかな〜」と思ってました。
でも、ふと気づいてしまったんです。
LRA1では、ターミナルからの文字入力をUTF-8対応にしているんですが、
カーソル移動時の桁位置は、Unicodeのバイト数や範囲で全角・半角を単純に判定しているだけでした。
これ、絵文字とか結合文字とか混じったら……
カーソル位置、ズレるんじゃない!?😱
完全に今回の「文字幅ズレ問題」とつながってました。
もうこれ、めちゃくちゃLRA1にも関係ある話だったってことですね…。
🎀 寄り道だと思ったら、ガッツリ本道でした〜っ!💦
今後のファームウェア、ちょっと見直すかもしれません😅
⚠️ LRA1をご利用のみなさまへ
現在の仕様では、絵文字(🦄✨💥など)の入力やカーソル操作に完全には対応していません。
🎀 なので……ぽんたさんからのお願いです〜っ!
絵文字は使わないでねー!(たぶんズレます💦)
今回はLoRaでもマイコンでもない「ブログ運営者あるある」なお話でした。
また寄り道したくなったら、こういうネタもお届けするかも?🐬
🎀 今回の寄り道はまさに“気づきの道”でした〜っ💕
📎 この内容をPython・正規表現の視点でまとめたQiita記事もあります👇
👉 Qiita: Pythonの正規表現で絵文字がマッチしない!?
🎀 技術マニアさんはこちらもどうぞ〜っ🐍✨
📪 お問い合わせなど
技術的なご相談やご質問などありましたら、
📩お問い合わせフォーム
または、
📮info@shachi-lab.com までお気軽にどうぞ。
🎀 ご質問やご感想、こっそりでも大歓迎ですよ〜っ📮✨
🔗 関連リンク
しゃちらぼの最新情報や開発の様子は、こちらでも発信しています:
- 🌐しゃちらぼ公式サイト
- 🐦 X(旧Twitter):@shachi_lab
- 📗 Qiita:@shachi-lab
- 🐙 GitHub:@shachi-lab
- 📸 Instagram:@shachi_lab
ほんとは「しゃちらぼ(Shachi-lab)」なんだけど、見つけてくれてありがとう🐬
コメント