Python: Unicodeのコードポイントと文字列の変換

2018-03-23

旧ブログ

t f B! P L
LibreOffice5(151)UnicodeのVariation Selector で「辻」のしんにょうの点の個数を変えるでコードポイントの使い方が分かったので、Pythonで文字列とコードポイントの関係を調べます。

コードポイントのシークエンスを文字列に変換する

LibreOfficeではAlt+xでコードポイントと文字の変換ができました。
Pythonでは4桁の16進数のコードポイントは\u、8桁の16進数のコードポイントは\Uでエスケープしてテキストシークエンス型(str)で与えるとその文字列が取得できます。
ユニコードポイントの16進数は大文字でも小文字でも同じ結果になるのですが、Pythonで処理した結果は小文字で返ってくるので小文字の16進数でコードポイントを書いています。
In [1]:
# UnicodeリテラルとASCII文字の文字列。
u = "\u30aa\u30d7\u30b7\u30e7\u30f3\u30dc\u30bf\u30f31"  
u
Out[1]:
'オプションボタン1'
このコードポイントはLibreOffice5(29)ダイアログエディタの言語ツールバーを利用する方法で作成したLibreOfficeの.propertiesファイルに出力されたものです。
Jupyter Notebookやコンソールでオブジェクトだけ渡したときに表示されるのはオブジェクトの印字可能な表現を含む文字列、つまりrepr()の戻り値です。
strを渡すとクォートされた文字列が返ってきます。
In [2]:
print(u)
オプションボタン1
print()を使えば文字列が出力されます。
LibreOffice5(151)UnicodeのVariation Selector で「辻」のしんにょうの点の個数を変えるのVariation Selectorは5桁の16進数なので\Uでエスケープして8桁の16進数にして渡します。
In [3]:
# Variation Selectorを使う。
t = "\u8FBB\U000E0100"  
t
Out[3]:
'辻󠄀'
コンソールがVS(Variation Selector)に対応していて、かつVSに対応したフォントを使っていればしんにょうの点が1個の「辻」が表示されるはずですが、Windows10のPowerShellはVSに対応していないようです。

文字列をコードポイントのシークエンスに変換する2つの方法

In [4]:
u
Out[4]:
'オプションボタン1'
この文字列のコードポイントを見るには関数ascii()を使う方法が簡単です。
In [5]:
# 文字列の中のUnicode文字をUnicodeリテラルにする。
a = ascii(u)
a
Out[5]:
"'\\u30aa\\u30d7\\u30b7\\u30e7\\u30f3\\u30dc\\u30bf\\u30f31'"
関数ascii()はrepr()の戻り値の文字列のうち非ASCII文字を\x、\u、\Uのいずれかでエスケープした文字列を返します。
ASCII文字はASCII文字のまま返ってきます。
Pythonのテキストシーケンス型のrepr()は通常はシングルクォートで返ってくるのでascii()に文字列を渡すした戻り値の文字列のrepr()はダブルクォートにシングルクォートが埋め込まれて返ってきます。
repr()ではエスケープシーケンス\uもエスケープしないとコードポイントが文字になってしまうので、\uがエスケープされています。
In [6]:
print(a)
'\u30aa\u30d7\u30b7\u30e7\u30f3\u30dc\u30bf\u30f31'
print()で出力するとrepr()の戻り値をstr()の結果として文字列のコードポイントを取得出来ます。
コードポイントを取得するもう一つの方法はPython特有のエンコーディングのunicode_escapeでエンコードする方法です。
In [7]:
# unicode_escapeでバイト列にエンコードする。
b = u.encode('unicode_escape')  
b
Out[7]:
b'\\u30aa\\u30d7\\u30b7\\u30e7\\u30f3\\u30dc\\u30bf\\u30f31'
unicode_escapeでエンコードするとコードポイントになったバイト列が取得できます。
これもASCII文字はASCII文字のまま返ってきます。
In [8]:
# バイト列をutf-8として文字列にデコードする。
d = b.decode()
d
Out[8]:
'\\u30aa\\u30d7\\u30b7\\u30e7\\u30f3\\u30dc\\u30bf\\u30f31'
コードポイントのままバイト列を文字列にするには、バイト列をutf-8エンコードとしてデコードして文字列にします。
In [9]:
print(d)
\u30aa\u30d7\u30b7\u30e7\u30f3\u30dc\u30bf\u30f31
print()すると元の文字列のコードポイントのシークエンスが取得できます。
以上、文字列をコードポイントのシークエンスに変換するの方法をまとめると次の2つになります。
In [10]:
u
Out[10]:
'オプションボタン1'
In [11]:
print(ascii(u)[1:-1])  # 関数ascii()を使う方法。
\u30aa\u30d7\u30b7\u30e7\u30f3\u30dc\u30bf\u30f31
In [12]:
print(u.encode('unicode_escape').decode())  # unicode_escapeでデコードする方法。
\u30aa\u30d7\u30b7\u30e7\u30f3\u30dc\u30bf\u30f31
VSがついたコードポイントもちゃんとコードポイントに戻せます。
In [13]:
t  # "\u8FBB\U000E0100"
Out[13]:
'辻󠄀'
In [14]:
print(ascii(t)[1:-1])  # 関数ascii()を使う方法。
\u8fbb\U000e0100
In [15]:
print(t.encode('unicode_escape').decode())  # unicode_escapeでデコードする方法。
\u8fbb\U000e0100

コードポイントと文字の名前の変換

Unicodeの文字はコードポイント以外に名前でアクセスできます。
unicodedata.name()でUnicode文字の名前が取得できます。
In [16]:
import unicodedata
unicodedata.name("辻")  # Unicode文字の名前を取得。
Out[16]:
'CJK UNIFIED IDEOGRAPH-8FBB'
今度はこの異体字の点一つの「辻」の名前を調べてみます。
In [17]:
t  # "\u8FBB\U000E0100"
Out[17]:
'辻󠄀'
In [18]:
unicodedata.name(t)  # tはVSを含んでいるので一文字ではない。
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-43317631bcf7> in <module>()
----> 1 unicodedata.name(t)  # tはVSを含んでいるので一文字ではない。

TypeError: name() argument 1 must be a unicode character, not str
これは1文字ではないと言われて名前が取得できません。
VSが一文字としてカウントされているからです。
二文字以上のユニコード文字の名前を一括して調べるにはasciiでエンコードして、asciiでエンコードできない文字を名前で出力するようにします。
In [19]:
# ASCII文字以外は名前にしてバイト列にエンコードする。
b = t.encode('ascii', 'namereplace')  
b
Out[19]:
b'\\N{CJK UNIFIED IDEOGRAPH-8FBB}\\N{VARIATION SELECTOR-17}'
バイト列で返ってくるのでこれをutf-8としてデコードして文字列にします。
In [20]:
# バイト列をutf-8として文字列にデコードする。
d = b.decode()
d
Out[20]:
'\\N{CJK UNIFIED IDEOGRAPH-8FBB}\\N{VARIATION SELECTOR-17}'
print()すると\Nでエスケープした名前が取得できます。
In [21]:
print(d)
\N{CJK UNIFIED IDEOGRAPH-8FBB}\N{VARIATION SELECTOR-17}
VSのU+E0100の名前はVARIATION SELECTOR-17とわかります。なのでVS17と略されると理解できました。
In [22]:
# \NでUnicode文字名をエスケープ。
"\N{CJK UNIFIED IDEOGRAPH-8FBB}\N{VARIATION SELECTOR-17}"
Out[22]:
'辻󠄀'
名前を\N{}に入れて文字列にするとその文字が取得できます。

異体字の長さにはVSが含まれてしまう

異体字は元になる漢字と見えない文字であるVSの2つのユニコード文字から成り立っているのでlen()で長さをみると1文字なのに2が返ってきます。
In [23]:
t  # "\u8FBB\U000E0100"
Out[23]:
'辻󠄀'
In [24]:
len(t)  # ユニコード文字としては2文字になる。
Out[24]:
2
スライスするとインデックスで一つずつユニコード文字が取り出せます。
In [25]:
t[0]
Out[25]:
'辻'
In [26]:
t[1]
Out[26]:
'󠄀'
VSにはグリフがないので何も出力されません。
In [27]:
v = t.replace(t[1], "")  # 2文字目を削除する。
v
Out[27]:
'辻'
In [28]:
len(v)
Out[28]:
1
VSを空文字に置換すると文字数は1個になりますが、この方法は汎用性がありませんね。

異字体を含む文字列の長さを取得する方法

In [29]:
k = "奈良県葛\U000E0100城市"  # VSを含んだ文字列。
In [30]:
k
Out[30]:
'奈良県葛󠄀城市'
メイリオなどAdobe-Japan1コレクションに対応したフォントの場合は「葛」の字が異体字の「葛󠄀」となって見えているはずです。
In [31]:
len(k)  # VSも1文字としてカウントされる。
Out[31]:
7
VSがカウントされるので見た目は6文字なのに7文字と返ってきます。
In [32]:
for p in k:
    if p:  # 空文字でないとき
        print(p)
奈
良
県
葛
󠄀
城
市
VSはTrueになるのでif文で空文字として除外できません。
In [33]:
for p in k:
    print(unicodedata.category(p))  # Unicodeの汎用カテゴリを出力。
Lo
Lo
Lo
Lo
Mn
Lo
Lo
Unicodeの汎用カテゴリを使えばVSを区別できそうです。
Unicode character property - WikipediaのGeneral CategoryがUnicodeの汎用カテゴリになります。
Mnは幅のない文字になります。
In [34]:
# 汎用カテゴリがMnのときは数えない。
len([p for p in k if unicodedata.category(p)!="Mn"])
Out[34]:
6
これで見た目通りの文字数が取得出来ました。

文字→コードポイント→文字

一つのUnicode文字だけであれば関数ord()とchr()が使えます。
In [35]:
# 文字をコードポイントに変換。
# 整数で返ってくる。
codepoint = ord("日")  
codepoint
Out[35]:
26085
In [36]:
# コードポイントを16進数の文字列にする。
hex(codepoint)  
Out[36]:
'0x65e5'
つまり「日」のコードポイントはU+65E5とわかります。
In [37]:
# コードポイントを文字にする。
chr(codepoint)  
Out[37]:
'日'
これで元の文字に戻せました。

(以下2018.7.27追記。)

文字列が半角カナのみかどうか判定する


txtに半角カナ文字列が入っているとして、

all(map(lambda x: "ア"<=x<="ン", txt))

で上手くいくと思ったのですが、これでは濁点やら小さいッなどが含まれていません。

データベース - UTF-8の半角カナについて(82922)|teratail

これによると半角カナのコードポイントは0xFF61~0xFF9Fの範囲とわかりました。

all(map(lambda x: chr(0xFF61)<=x<=chr(0xFF9F), txt))

これでうまくいきました。

txtが半角カナしか含まない時はTrueが返ってきます。

all(map(lambda x: '。'<=x<='゚', txt))

コードポイントを文字にするとこうなります。

参考にしたサイト


Unicode character property - Wikipedia
General Categoryがunicodedata.category()の戻り値の一覧になります。

文字列およびバイト列リテラル
Pythonのエスケープシーケンス一覧。

データベース - UTF-8の半角カナについて(82922)|teratail
半角カナ、漢字、全角記号、全角英数及びマーク、のコードポイントの範囲が書いてあります。

次の関連記事:Python: Unicodeのコードポイントとバイト列との変換

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ