[[ノート/テキストマイニング]]~

訪問者数 &counter();      最終更新 &lastmod();~

**PythonのUnicode/UTF-8文字列の扱い --- 2012/02/29 [#p2a6cd72]


***UnicodeとUTF-8 [#ff404d5d]
Unicodeは、 [[WikipediaのUnicode:http://ja.wikipedia.org/wiki/Unicode]] によると
 世界中の多くのコンピュータ上の文字列を一貫した方法で符号化し、表現し、
 扱うためのコンピュータ業界の標準
で、
 NT系のMicrosoft Windows (Windows)、Mac OS X、LinuxやJavaなどで利用されて
おり、
 元々16ビットの文字集合で全ての文字の網羅を目指して開発されたが、
 符号位置が圧倒的に足りなくなり、Unicode 2.0以降では、
 21ビットの文字集合として規定されることとなった。
である。
 Unicodeの文字を表現する場合、"U+"にその文字の符号位置を表す16進数の値を続ける。
また、
 日本の文字については当初より JIS X 0201、JIS X 0208 と JIS X 0212 を、
 Unicode 3.1 では JIS X 0213 の内容を収録している。
具体的に我々が使いそうなのは、
 U+0000-007F  ラテン文字(ASCII文字)
 と
 U+3000-303F  漢字上の記号・句読点
 U+3040-309F  ひらがな
 U+30A0-30FF  かたかな
 U+4E00-9FFF  漢字(正確にはCJK統合漢字、CJKは中国・日本・韓国)
と思われる。あと、CJK互換文字とかCJK統合漢字拡張B,C,Dとかあるが、無視してよいのか?

UTF-8は、上記のUnicodeを「符号化」する方法の1つである。[[WikipediaのUTF-8:http://ja.wikipedia.org/wiki/UTF-8]]によると、
 ASCII文字と互換性を持たせるために、ASCIIと同じ部分は1バイト、その他の部分
 を2〜6バイトで符号化する。4バイトのシーケンスでは21bit(0x1FFFFF)まで
 表現することができるが、(後略)
であり、符号化の規則は、
 バイトの先頭が0 ⇒ 1バイトコード(ASCII対応のU+00-7Fの部分)
 バイトの先頭が10 ⇒ 2〜6バイト時の2〜6バイト目(非1バイト目)を表す
 バイトの先頭が110 ⇒ 2バイトコードの1バイト目
   2バイトコードはU+0080-07FFを収容する。7FF=11ビットをyyyyxxxxxxxとすると
    1バイト目 110yyyyx、 2バイト目 10xxxxxx として収める
 バイトの先頭が1110 ⇒ 3バイトコードの1バイト目
   3バイトコードはU+08000-FFFFを収容する。FFFF=16ビットをyyyyyxxxxxxxxxxxとすると
    1バイト目 1110yyyy、 2バイト目 10yxxxxx、 3バイト目 10xxxxxx として収める。

***Pythonでの、Unicode文字列とUTF-8文字列の違い [#x15e1489]
ここは、かなり微妙な話で、わかりにくかったが、[[このページ:http://lab.hde.co.jp/2008/08/pythonunicodeencodeerror.html]]を見て納得した。

要するに、Python上では~
Unicode文字列 = 次項に述べるように、「シーケンス型」のうちの1つで、propertyとして「unicode」がついているもの。だから、
 >>> u1 = u"これはユニコード型文字列です"
 >>> type(u1)
 <type 'unicode'>
UTF-8の文字列 = シーケンス型のうちの「str型」であり、バイト列と思えばよいらしく、その中に入っている文字のエンコードは(pythonとしては)関わらない。たまたまシステムがUTF-8を仮定しているのでUTF-8のバイトコードが入っているだけ。
 >>> s1 = "これはふつうの文字列です"
 >>> type(s1)
 <type 'str'> 
Pythonのシステムに対して、「扱う文字コードはUTF-8」としておけば(=プログラム上に書く文字列も、入出力もUTF-8としておけば)、str型の中に入っている文字列はUTF-8になっている。でも、ユニコード型とは、同じものではない。また、ユニコード型の文字列が内部的にUTF-8で表現されている保証もない(実際どうなのか知らない)。

目に見える違いとしては、たとえば
 # EUC-JPなソースコードでは
 >>> len("あ")   〜〜〜 EUC-JPやShift-JISでは2バイトで保持される
 2
 # UTF-8なソースコードでは
 >>> len("あ")   〜〜〜 UTF-8では3バイトで保持される
 3
 # unicode型なら
 >>> len(u"あ")  〜〜〜 ユニコード文字としては1文字(内部表現のバイトではなくて)
 1 
ということになる。

で、基本は、プログラム内部ではなるべくユニコード型で持つべし、ということになる。変換が必要になることがある。
-プログラム内に文字列で書くときは、u"あいうえお"のようにuを付ける
で、基本は、プログラム内部ではなるべくunicode型で持つべし、ということになる。変換が必要になることがある。
-プログラム内に文字列で書くときは、u"あいうえお"のようにuを付けるとunicode型になり、またuを付けずに"かきくけこ"のように書くとstr型になる。
-標準入出力については、
 import sys
 import codecs
 
 sys.stdin  = codecs.getreader('utf_8')(sys.stdin)
 sys.stdout = codecs.getwriter('utf_8')(sys.stdout)
を使う。同様にファイル入出力に対しては
 import codecs
 
 fin  = codecs.open('input.txt', 'r', 'utf_8')
 fout = codecs.open('output.txt', 'w', 'utf_8')
のようにすることができる。なお、utf_8の代わりにshift_jisやeuc_jpを指定することもできる。
-変数間で変換するには、
 u1 = u"あいう"   〜〜〜 u1はunicode型
 s1 = u1.encode('utf_8') 〜〜〜 s1はstr型
や、逆に
 s1 = "かきく"   〜〜〜 s1はstr型
 u1 = s1.decode('utf_8') 〜〜〜 u1はunicode型
ができる。また組込み関数unicodeはdecode('utf_8')と同じような働きをする
 u1 = unicode(s1, 'utf_8')
ができる。


***Pythonでのunicode文字列とその分解 [#a2923808]
まず、Pythonでのunicode文字列は、「シーケンス型」である。([[5.6節 シーケンス型:http://www.python.jp/doc/release/library/stdtypes.html#str-unicode-list-tuple-bytearray-buffer-xrange]])

シーケンス型への操作は、この5.6節の表に載っている通りで、
|x in s|s のある要素 x と等しい場合 True , そうでない場合 False|
|x not in s|s のある要素が x と等しい場合 False, そうでない場合 True|
|s + t|	s および t の結合|
|s * n, n * s|s の浅いコピー n 個からなる結合 |
|s[i]|s の 0 から数えて i 番目の要素|
|s[i:j]|s の i 番目から j 番目までのスライス|
|s[i:j:k]|s の i 番目から j 番目まで、 k 毎のスライス|
|len(s)|s の長さ |
|min(s)|s の最小の要素 |
|max(s)|s の最大の要素|
|s.index(i)|s 中で最初に i が現れる場所のインデクス |
|s.count(i)|s 中に i が現れる数|

だから、s[i]を使って文字列を先頭からスキャンして判定することになる。

Pythonでの、文字列がUnicodeであるかどうかの判定は、実は簡単で、
1文字ずつスキャンする必要はない。シーケンス型の変数xがUnicodeであるかどうかは、
 isinstance(x, unicode)
によって判定できる。

しかし、これは今の役には立たない。なぜなら、Twitter APIで得られたtext本文は、
すべて(ASCII文字も含めて)Unicodeで書かれているからである。たとえば、 u'abc'
など。従って、ASCII文字であるか漢字(2バイトコード)であるかの区別は、
1文字ずつ判定をする必要がある。

実際に欲しいのは、文字列全体の中に漢字(2バイトコード)が含まれているか否か、であるから、1文字でも漢字が出てくればそこでTrueを返せばよい。全部の文字を確認する必要はない。つまり
 Unicode文字列であることを確認したうえで、
 先頭から1文字ずつ見て行って、
    もしord(文字)が30xxや4Exx-9xxxならば、それは漢字なので「漢字入り文字列」。
    もし最後のバイトまでそうでなければ、「ASCII文字列」
である。
 # -*- coding: utf-8 -*-
 def hasKanji(s):
   #Returns True if string s contains a Kanji, False if not.
   kanji = False
   for c in s:
    if (((ord(c)&0xff00) == 0x3000) or (0x4e00 <= ((ord(c)&0xff00) <=0x9f00))):
      kanji = True
      break
   return kanji
 
 s = u"abc" とか u"abcあ" とか
 print hasKanji(s)

***Twitter APIとhasKanjiを組み合わせる [#o7bd152e]
 # -*- coding: utf-8 -*-
 import twitter
 def hasKanji(s):
   #Returns True if string s contains a Kanji, False if not.
   kanji = False
   for c in s:
    if (((ord(c)&0xff00) == 0x3000) or (0x4e00 <= ((ord(c)&0xff00) <=0x9f00))):
      kanji = True
      break
   return kanji
 
 api = twitter.Api()
 timeline = api.GetPublicTimeline()
 for s in timeline:
   t = s.text
   if hasKanji(t):
     print "Kanji> ", t
   else:
     print "Ascii> ", t

実際やってみると、Kanjiのツイートはそれ程多くないものだ。時間帯によるのか?

次のステップは、選び出した漢字のツイートを日本語形態素解析に掛けることになる ⇒ [[ノート/テキストマイニング/twitter-2]]

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS