たにしきんぐダム

プログラミングやったりゲームしてます

Pythonでの文字コードの取り扱い

定期的にこの手の記事が上がってる気がするけどあげていく。

unicode型とかstr型とかいう言い方するとわかりにくいけど、データの実体がどういうものかわかってれば理解しやすいよね。

参考

Unicode

Unicode では全ての文字にID(コードポイント)(0 ~ 0x10FFFF)をふっている。コードポイントを表す時は U+{16進数} と書く。

  • ord()により、ある文字に対応するコードポイント(の10進数表記を得ることができる)
  • chr()により、あるコードポイント(の10進数整数)から対応する文字を得ることができる
>>> ord("A")
65
>>> hex(ord("A"))
'0x41'
>>> chr(ord("A"))
'A'

ちなみに各コードポイントは文字に必ずしも割り当てられているわけではない。サロゲートコードポイントなどがその一例だが今回はその説明については省略する。

文字符号化方式

Unicodeにより各文字にはそれぞれコードポイントが割り当てられいる、それではこれをコンピュータによってどのように表現するかであるが、 UTF-8 UTF-16 UTF-32 といった符号化方式が存在する。それぞれ 8bit 16bit 32bit を単位とする符号化方式である。

UTF-16

16bitで表すことのできる U+0000 ~ U+FFFF までは1符号単位(16bit)により表す。

16bitだけで表現できない U+10000 ~ U+10FFFF はサロゲートペアという二つの符号単位の塊(16bit * 2)により表現する。

サロゲートペアを構成するコードポイントはサロゲートコードポイントと呼ばれる、サロゲートコードポイントはさらに以下のように分けることができて

上位サロゲートと下位サロゲートによりサロゲートペアを構成する

UTF-8

8bitを符号単位とする符号化方式 1byteで表現できる U+0000 ~ U+007F は1byte 2byteで表現できる U+0080 ~ U+07FF は2byte というようにコードポイントによって異なる長さのバイトシーケンスによりいい感じに符号化を行う

ASCII

ASCIIは7bitにより表される各整数(0~127)に対して文字を割り当てた符号化方式 ord() により得られる整数(0~127)は Unicode code point でありかつ ascii code でもある。 unicode code point 0~127 の文字に関しては Unicode と ascii は互換性がある。

Python3 における str型 と bytes型

ここまできたらPythonにおけるunicode型とstr型が理解できると思う。

Python3では str型Unicode文字(Unicode code point のシーケンスで表された文字)を実体としており、 bytes型utf-8utf-16やasciiといった符号化方式でエンコードされたバイトシーケンスが実体としている。

>>> type("🍣")
<class 'str'>
>>> hex(ord("🍣"))
'0x1f363'  # Unicode code point
>>> "🍣".encode('utf-8')  # unicode code point を utf-8 でエンコード
b'\xf0\x9f\x8d\xa3'  # バイトシーケンス
>>> type(b'\xf0\x9f\x8d\xa3')
<class 'bytes'>
>>> b'\xf0\x9f\x8d\xa3'.decode('utf-8')  # バイトシーケンスをutf-8でエンコードされたものだと思い込んで、unicode code point にデコード
'🍣'

Python3でのデフォルトエンコーディングUTF-8 バイトシーケンス<--->Unicode code point のエンコード/デコードはデフォルトではutf-8で行われる

>>> import sys
>>> sys.getdefaultencoding()
'utf-8'

ファイルの先頭で以下のようにエンコーディングを指定することもできる

# coding: -*- <encoding name> -*-

UnicodeDecodeError/UnicodeEncodeError

バイトシーケンス => Unicode code point への変換を考えてみる

# ascii互換の文字(code point 0~127 の文字)は utf8 バイトシーケンス を
# ascii バイトシーケンスだと思って decode しても unicode code point が得られる
# 0~127 の文字はutf-8でも 1byte だし (128~255 はasciiではない...)
>>> 'A'.encode('utf-8').decode('ascii')
'A'
# ascii非互換の文字のutf-8バイトシーケンス取得
>>> 'あ'.encode('utf-8')
b'\xe3\x81\x82'
# これをasciiだと思ってDecodeを試みる
# \xe3 などは 0~127 外(非ascii) なのでDecodeに失敗する(UnicodeDecodeError)
>>> 'あ'.encode('utf-8').decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position0: ordinal not in range(128)

次に Unicode code point => ascii バイトシーケンスへの変換を考えてみる

# Unicode code point (str型) をバイト列(ascii encode)に変換する際
# コードポイントが0~127ならOK
# それ以外ならascii encode に失敗 (UnicodeEncodeError)
>>> 'A'.encode('ascii')
b'a'
>>> 'あ'.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character '\u3042' in position 0: ordinal not in range(128)

補足 Python2 の str型 と unicode

Python2におけるstr型はPython3のそれとは違う

  • Python2のstr型 = Python3のbytes型 = バイトシーケンス
  • Python2のunicode型 = Python3のstr型 = Unicode code point シーケンス

混乱するね

Python3では 'a' といった表記の文字列は Unicode code point で表されていたが、 Python2では 'a' はバイトシーケンスで表される。 Python2でユニコードコードポイントを得るには u'a' このように書く

>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=13, releaselevel='final', serial=0)
>>> type('a')
<type 'str'>  # ここで言うstrはバイトシーケンスであることに注意
>>> type(u'a')
<type 'unicode'>  # これは unicode code point

Python2 のデフォルトエンコーディング

Python2 でのデフォルトエンコーディングはascii

>>> import sys
>>> sys.getdefaultencoding()
'ascii'

なので

# python3 ではこれは utf-8 エンコード、デコードだが
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=1, releaselevel='final', serial=0)
>>> 'あ'.encode()
b'\xe3\x81\x82'
>>> b'\xe3\x81\x82'.decode()
'あ'
>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=13, releaselevel='final', serial=0)
# unicode code point を ascii エンコードしようとする、「あ」はasciiの範囲外
>>> u'あ'.encode()
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal in range(128)

Scala関西Summit2017に参加/LTしました #scala_ks

参加しました!今年は4トラックもあってScalaコミュニティの盛り上がりを感じる良いイベントでした。 https://skug.connpass.com/event/62304/

ついでにScala製インタプリタをブラウザで動かす3分クッキングというタイトルでLTもしてきました。


今年のトーク内容は入門内容から、Scala導入事例紹介、Akka、FunctionalProgramming、言語コア、設計などなど多岐に渡っており、いろんな人に門戸を開いたバランスの良いカンファレンスだなと思いました(運営のみなさまありがとうございます)

個人的にはこのあたりのトークが特に興味深かったです。

Scalaインタプリタをブラウザで動かす3分クッキング

インターネットにある資料を参考にScalaでおもちゃレベルのmini-MLインタプリタを昔作っていたのでそれを引っ張り出して、Scala.jsを使ってブラウザで動かして遊んでみたよという話。 似たような取り組みは id:motemen さんが3年前にやってたり、いろんな人がやってると思います。

(あらかじめこちらに作ったものが…という3分クッキングあるあるネタが受けたのはよかった)

Scala.jsを使うのは初めてだったけれど、パーサーコンビネータに(Scala.jsに公式対応してる)fastparseを使っていたのもあってシュッとビルドできたし、Annotationによるexportするjsモジュールの指定や、crossProjectを使ってjs向けにだけビルドしたい部分でJVM向けにだけビルドしたい部分とコアロジックをうまく切り分けて管理・テストできるのも便利だった。

勉強用に作ったものなのでこれからも継続して勉強しながらいろいろ実装していきたい٩( ‘ω’ )و


今年もScala関西Summit楽しかったです!開催してくださった運営・スピーカー・スポンサーのみなさんありがとうございました!

YAPC::Fukuoka 2017 HAKATA に行ってきた

yapcjapan.org

特に発表はしませんでしたが、聴講のみで参加してきました ブログを書くまでがYAPCなので感想エントリを書いておく。

分散ユニークID採番機katsubushiとWebアプリケーションへの応用例

分散ユニークID採番機 katsubushi と Web アプリケーションへの応用例 / katsubushi // Speaker Deck

個人的に一番良かったトークsnowflake方式のbit形式で、1ホスト1プロセス起動するマイクロサービスとして利用される。

bit形式は上位から

  • 最上位bitは0固定、signed 64bit integer 対応のため
  • Timestamp: 41bit Epochからの経過時間(ms)
  • WorkerId: 10bit クラスタ内で一意のID
  • Sequence: 12bit 同一Timestamp内での連番

一番気になったのはWorkerIDをどう一意に取得するかでした、WorkerIdが重複してしまうと同一時刻に採番したIDが重複して破滅する。手でぺたぺた重複しないように採番しても良いが、動的にサーバーが起動する場合はどうするのか。発表内では

  • IPアドレスから採番(第3,4octetから計算)
  • IPが使えない場合は fujiwara/raus により指定範囲内(ここでは0~1023)のIDを取得

という方法が紹介されていた。IPから決める方法は順当だなと思いつつ、WorkerIDが10bitしかないのでIPの設定しくったら一瞬で破滅しそうで不安だなと思いながら発表を聞いていたら fujiwara/raus というソフトウェアが紹介された、これはRedisのPubSubを使って指定範囲内のIDを重複なく取得するという優れもの。その発想はなかった…すごい…

ID採番に関する知見が大量に得られる良いトークでした。

Web application good error messages and bad error messages

Web Application Good Error Message (and Bad Error Message) // Speaker Deck

良いエラーメッセージとはどういうものか、悪いエラーメッセージはどういうものかというトーク

  • 何が起きているか(必須)
  • 簡潔であること(好ましい)
  • 対処手段が提示されていること(最高)
  • 解決手段が提示されていること(最高)

確かに下に行けいくほど(トーク内ではエラーメッセージの次元が高くなると表現していた)良いエラーメッセージだというのは何となく思っていたが、それが整理されて良かった。エラーメッセージが無い、誤ったエラーメッセージが返るのはとにかく最悪なのでやめるゾ。

Web API の未来

YAPC::Fukuoka 2017 HAKATA // Speaker Deck

GraphQLの話、GraphQLは一切触ったことが無いのだが、RESTなAPI設計を心がけるとフロント側がたたくAPIの数が大量に出てきて辛いという話は共感があって、それを解決するのがGraphQLとのこと。 でもGraphQLってそれのためのAPI生やさないといけないんでしょ?って思っていたがフロントとバックエンドの間に GraphQL proxy を挟んで、フロントとproxyはGraphQLで会話するけど、proxyとバックエンドは普通に従来のREST APIでやりとりするみたいなことしても良いんですね。

スキップしていいテスト、スキップしてはいけないテスト 〜速さと信頼を兼ねたテストコードを構築する術〜

スキップしていいテスト、スキップしてはいけないテスト 〜速さと信頼を兼ねたテストコードを構築する術〜 / Need for speed of testing in Perl5 Web Application. // Speaker Deck

テスト書いてますか?という啓蒙的な話ではなくて、肥大化してきて大量のテストをいかに短い時間で終わらせるようにするかという話、一般的な話というよりはケーススタディ。テスト書いていることは当たり前でどうテストを郭嘉が重要な時代

テストがめちゃくちゃ重いと、リリース前のフルテストに大量の時間をくってしまう。

  • テスト用のダミーユーザーをDBにINSERTするのに時間がかかっていた。
    • 関連テーブルが20個以上、フルテストなら1900ユーザー程度を挿入
    • フルテストのはじめにbulk insert、ジェネレートメソッドははじめにbulk insertしたユーザーを1つずつ返していく。
    • フルテストが30分->19分に(すごい)(ユーザーの挿入に11分かかってたって…)
  • 金の弾丸
    • はい
  • 同じテスト何回も走らせても意味ないだろう

テストはもうみんな書いてるでしょうっていう話が良かった。

福岡のIT企業さんが福岡の良いところを紹介していた。福岡は(東京と比べて)家賃やオフィス代が安いし、エンジニア文化の活気がある。東京からUターンする人も福岡でキャッチできる。 僕は福岡出身なので福岡が盛り上がるのは嬉しいなと思ったのと、関西もそんな感じなので是非関西にIT企業増えてくれと思った。LINE福岡さんのオフィスはでかくて良かった、ありがとうございました!

次回は沖縄OIST、沖縄行きたい。

yapcjapan.org

JSで CodePoint 数えたい

ここで一句

JSで文字列を16bit単位ではなくUnicode Code Point単位で数える方法はいくつかあるが、結局2017年5月時点で(IE11のようなブラウザも含めて)ほとんどの環境で動作する方法はどれなんだろう。調べたのでまとめておきます、ご指摘あればどしどし(ง ‘-’ )ง

参考

Unicode コードポイント

Unicode では全ての文字にID(コードポイント)(0 ~ 0x10FFFF)をふっている。 コードポイントを表す時は U+{16進数} と書く。

UTF-16 では U+0000 ~ U+FFFF までのコードポイントは 16bitの1符号単位で表現されて U+10000 以降のコードポイント(𩸽のコードポイントなど)は、16bitだけでは表現できないため後述するサロゲートペアで表現される。

サロゲートペア

UTF-16 で16bitだけでは表現できないコードポイントを表現するために、一部の文字(U+10000 以降のコードポイントに対応する文字)は 16bit x 2 の 32bit で表現する。これらの文字(表現)をサロゲートペアと呼ぶ。

サロゲートコードポイント

サロゲートペアを構成する 16bit の値の範囲は U+D800 ~ U+DFFF であり、これらをサロゲートコードポイントと呼ぶ(このサロゲートコードポイントには文字が割り当てられていない)。さらに

と呼び、上位サロゲートと下位サロゲートの組み合わせでサロゲートペアを表現する。

lengthプロパティ

JavaScriptstr.lengthUnicodeコードポイント単位での文字列の長さではなく、UTF-16 符号単位の長さを返す。

例えば「𩸽」はサロゲートペアであるため、length は 2 となってしまう。

"𩸽".length; // 2

「𩸽」のようなサロゲートペアも1としてカウントしたい。そのためにはコードポイント単位での長さを調べれば良い。

どうやってコードポイント単位の長さを調べるか

いかれたメンバーを紹介するぜ!

for of

ES2015で追加された for ... of... を使うとコードポイント単位で繰り返しが可能

for (let c of '𩸽定食') console.log(c)
// 𩸽
// 定
// 食

しかし現在(2017年5月)IE11で未対応 https://kangax.github.io/compat-table/es6/#test-for..of_loops

スプレッド演算子

分割代入時の分割もコードポイントが意識されている模様。 スプレッド演算子を使えばシュッとコードポイント単位で文字列を配列に変形できる。

[...'𩸽定食']
// ['𩸽', '定', '食']

これも現在(2017年5月)IE11未対応 [https://kangax.github.io/compat-table/es6/#test-spread(…)operator]

RegExp unicode flag

ES2015より unicode flag が導入された、このフラグを使うとパターンをコードポイントの羅列として扱ってくれる。

'𩸽定食'.match(/./ug);
// ['𩸽', '定', '食']

残念ながら unicode flag も現在(2017年5月)IE11未対応 https://kangax.github.io/compat-table/es6/#test-RegExp_y_and_u_flags

for文で頑張る

文字列を16bit単位でループし、サロゲートペアは1としてカウントする。

function stringLength(str) {
  let count = 0;
  for (let i = 0; i < str.length; i++) {
    count++;
    // i番目の 16bit が得られる
    const code = str.charCodeAt(i);
    if (0xD800 <= code && code <= 0xDBFF) {
      // i番目の 16bit が上位サロゲートなら
      // 次の 16bit (下位サロゲート) はスキップ
      i++;
    }
  }
  return count;
}

これならIE11でも動く

正規表現(Unicode Sequence)

サロゲートペアを非サロゲートペア文字に変換してlengthをとる

function stringLength(str) {
  // サロゲートペアを _ にreplace
  return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length;
}

stringLength('𩸽定食'); // 3

配列に変換したい場合

function stringToArray(str) {
  return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
}

stringToArray('𩸽定食');
// ['𩸽', '定', '食']

まとめ

以上だ!

随時更新していきたい

株式会社はてなに入社しました

こんにちは、id:tanishiking24 といいます。 2017年4月より株式会社はてなでWebアプリケーションエンジニアとして働くことになりました。京都オフィス勤務です。

これまでは京都大学工学部情報学科という所に通っていました。 他の企業とも迷ったのですが、はてなのサービスが好きで馴染みがあったり、勉強会やSNSでの社員さんの印象が良かったり、給与が良かったりのあれこれではてなが最高そうだなと思い、お世話になることにしました。頑張るぞ

(初日の朝の自己紹介で「バリバリバリューを出していきたい」みたいなことを言ったらフレッシュ感がないと言われてしまい失敗したなと反省しています、これからフレッシュさを取り戻していきたい。)