ラベル Python の投稿を表示しています。 すべての投稿を表示
ラベル Python の投稿を表示しています。 すべての投稿を表示

2012/12/26

文系のための「ファイル」(2)

コンピュータ上の様々なファイルをダブルクリックを押すとどうなるか?
ひょっとしたら、最適なソフトウェアが無いなどと言われるかもしれないが、
大抵の場合は、そのファイルに最適なソフトウェアが起動する。

当たり前のことであるが、ダブルクリックという操作が行われた瞬間に、
コンピュータは、そのファイルの「種類」に最適なプログラムを検出し、
検出した種類に適したなプログラムを起動するように命令している。

つまり、ファイルには、何らかのファイルの種類を特定する情報があって、
コンピュータは、仕組み上、その種類を識別できる…ハズである。
今回の話は、コンピュータがどのようにしてファイルの種類を特定するか?という話。

結論から言うと、ファイルの種類は「拡張子」と呼ばれるものによって判別される。
前回の話では、FATというファイルシステムの所で少し話をした。
8.3形式では、8文字のファイル名と3文字の拡張子が云々…とか。

どういうことか?とりあえず、8文字のファイル名と3文字の拡張子の例を挙げる。

abcdefg.txt

この場合、「abcdefg」の部分が「ファイル名」であり、
「.」を挟んで「txt」の部分が「拡張子」と呼ばれる部分である。
知っている人が見れば、上のファイルがメモ帳で開けるテキストファイルだと解る。

同様に、知っている人が見れば以下のファイルの種類を知ることは極めて簡単。
  • untitle.doc
  • untitle.jpg
  • untitle.mp3
この三種類のファイルの種類は一番上から順に、
Wordの文書データ、JPEG形式と呼ばれる画像データ、MP3と呼ばれる音声データ、
ということが解る。これらは、ファイル名の後ろに付与されている拡張子で判別できる。

ここまでに挙げた例は、8.3形式というのは、昔のFATでの規則の場合。
したがって、他のファイルシステムでは、3文字以外の拡張子を使うこともあるし、
拡張子を複数付けることもある。
  • untitle.JPEG
  • untitle.py
  • untitle.tar.gz
これらは、JPEG形式のファイル、Pythonのファイル、そして、最後のファイルは、
複数のファイルをまとめて(tar)、GZIPという方法で圧縮した(gz)ファイル。
なお、現在では、ファイル名の文字数制限に関しては気にする必要は無いだろう。

さて、拡張子は、要するに、ファイル名の後ろに「.」を挟んで付加する文字列で、
その文字列によって、ファイルの種類を特定するためのものである。
この仕組みによって、コンピュータはファイルの種類を特定しているのである。

ところで、このブログの読者の中には、自分のコンピュータの環境上、
拡張子が表示されていない可能性があるが、その設定方法は、
例えば、「Windows7 拡張子 表示」などとGoogleで検索すると出てくるハズ。

Mac OS X や Ubuntu も同様に、検索すると出てくるので各自設定方法を検索して確認を。
OSやバージョンごとに、設定方法が異なるので、このブログでは詳しくは書かない。
不親切かもしれないが、このブログよりも親切な説明を見た方が良いだろう。

少々、話が逸れたが、拡張子については、セキュリティ上のこともあって、
ちゃんと理解した方が良い。というのは、拡張子は「偽装」できるからである。
この「偽装」というのを使って、コンピュータウイルスをばら撒く連中がいる。

例えば、Windows の場合、直接実行できるファイルの拡張子は「.exe」である。
要するに、「execution(執行)」の略。一般的なソフトウェアの拡張子も「exe」。
拡張子が見えない設定の場合、以下のように拡張子を偽装することができる。
  • I_LOVE_YOU.txt.exe
一般的に、複数の拡張子が並んでいる場合、最後の「.」から後ろを拡張子とする。
Windows などで、拡張子が非表示になっている場合には、最後の拡張子が省かれる。
したがって、このファイルの拡張子は、以下のように見えてしまう。
  • I_LOVE_YOU.txt
これを単なるテキストファイルであり誰かからのラブレターである、と勘違いし、
ウハウハ気分でダブルクリックすると、このファイルが実行されてしまい、
結果としてウイルスに感染する...。

この事件、実際に過去に起きた事件。2000年ごろに世界中で話題になった。
なお、実際の拡張子は「exe」では無く、「vbs」という拡張子であったが、
この拡張子を持つファイルも直接実行が可能で、多くの人が被害にあった。

ちなみに、私は騙されなかったが...騙された人は周囲にもいた。

本来は、拡張子のことを知らない人が勝手に書き換えて、
ファイルが適切に開けないようにするために拡張子を表示しないようになっているが、
このブログの読者には、拡張子を表示しておくことを強く推奨する。

拡張子の偽装は、悪意があっての偽装とは限らない。
例えば、最近のワードファイルの拡張子は「.docx」である。
実は、このファイル、ZIPという形式で圧縮されたファイルである。

したがって、このファイルの拡張子を「.zip」に変更すると...。
普通に、このファイルを開くことができる。
危険なので、そのようなことはしてはいけないが、実験してみると良い。

さてさて、ここからは、Pythonを使って、拡張子を扱ってみる。
この方法も、自分でプログラムを作る時に役立つ。
とりあえず、Pythonコンソールを立ち上げる。

以下は、ディレクトリの話の所で使ったコードを修正したもの。
とりあえず、自分のホームディレクトリを設定して実行。
意味が解らない人は、前の話に戻って復習。


import os

home = "ここに自分のホームディレクトリのパスを書く"

for path in os.listdir(home):
    fullpath = os.path.join(home, path)
    if os.path.isdir(fullpath) == False:
        filename, extension = os.path.splitext(path)
            print("nam:"+filename)
            print("ext:"+extension)

前回の話の時には、ディレクトリを取得するのが目的だったが、
今回は、ファイル名と拡張子を取得したいため、「if os.path.isdir(fullpath) == False:」。
つまり、fullpath に格納されたパスがディレクトリ名では「無い」場合。

この処理で、最終的にコンソールに表示されるのは、printと書かれている部分の中身。
「nam:○○○○」という表示と「ext:××××」というのが交互に表示されるはず。
このプログラムで重要な部分は、print の命令の直前の部分。

つまり、「filename, extension = os.path.splitext(path)」と書かれた部分。
この部分では、「os」というパッケージに含まれる「path」というパッケージの中の、
splitext() という機能を呼び出し、「filename」と「extension」に同時に結果を格納している。

これを応用すると、特定のファイルのみを取得することができる。
例えば、テキストファイルのみを取り出したい場合には、以下のようにする。

import os

home = "ここに自分のホームディレクトリのパスを書く"

for path in os.listdir(home):
    fullpath = os.path.join(home, path)
    if os.path.isdir(fullpath) == False:
        filename, extension = os.path.splitext(path)
        if extension == ".txt":
            print(fullpath)

「if extension == ".txt":」という部分は、「もしも、拡張子が”.txt”ならば:」という分岐。
この結果が満たされた場合にのみ「print(fullpath)」が実行され、絶対パスが表示される。
ここで、JPEG画像を表示したい場合には、「.txt」の部分を「.jpg」に変えれば良い。

2012/12/18

文系のための「ディレクトリ構造」(2)

ディレクトリの基本的な用語を整理したところで、
今度は、実際にPythonを使ってディレクトリに関する知識を深めてみる。
今回の話は、今後、自分でプログラムを書く上で役立つハズ。

さて、本題に入る前に、二つほど質問。
あなたが使っているパソコンの「OS」は何か?
また、そのOSの「バージョン」は何であるか?

この質問の意味が解らない人は、
文系のための「情報管理」の話を最初から読んだほう良い。
おそらく、この先の話、何を書いているか、理解するのが難しい。

さて、すでに述べたように、Windows系のOSとUnix系(Mac OS Xも含む)のOSでは、
ディレクトリの表示方法が異なるのであった。忘れている人は前の話を参照。
また、バージョンによっては既定のディレクトリの配置場所も異なる。

ところで、OSには、シングルユーザ環境と、マルチユーザ環境というのがある。
一人で使うことを前提としているのか?、複数で使うことを前提としているのか?
自分が使っているパソコンのOSの環境はどちらであろうか?

複数の人が一つのパソコンを使う際には、データの保存場所や、
デスクトップの壁紙、メールの設定など、個人設定の領域を分けれた方が良い。
マルチユーザ環境とは、要するに、こうした状況を可能にすることである。

このブログの読者で、Unix系のOSの人は、かなり高い確率でマルチユーザ環境。
Windows系のOSの人は、Windows 2000 以降の人はマルチユーザ環境であり、
Windows 95、Windows 98、Windows MEの人は、おそらく、シングルユーザ環境。

実を言うと、Windows XPでさえも、すでにサポートが打ち切りになっているので、
上に挙げたWindows系のOSを使っている人は、新しいOSに変えた方が良い。
「なりすまし事件」が話題になったが、サポートが切れたOSを使うのは極めて危険。

さて、話を戻そう。なぜ、この話を最初にしたかというと、
今回は「ホームディレクトリ」という特殊なディレクトリを扱うため。
これを意識している一般ユーザは少ないかもしれないが、非常に重要。

ホームディレクトリというのは、ユーザが自由に読み書きできるディレクトリのこと。
OSの根本に関わる部分や、様々なアプリに関わる部分というのは、非常にデリケートで、
知らない人が触ると、最悪の事態として、コンピュータが動かなくなる。

可能な限り、通常のユーザがシステムに関わる部分を傷つけないようにするために、
基本的にはホームディレクトリに個人に関わるファイルを纏めるようになっている。
また、このようにすることでマルチユーザ環境でユーザごとの設定も分けやすくなる。

もっとも、どれをいじってもシステムに影響しないとは言えないが…。

さて、今回は、そのホームディレクトリのディレクトリで色々と実験する。
では、肝心のホームディレクトリはどこにあるのか?
実は、OSとOSのバージョンによって異なる。

以下、比較的に有名なOSとそのバージョンごとのホームディレクトリの場所。
自分が使っているOSが下に無い場合は、自分で検索を。流石に網羅できない…。
  • Windows系
    • Windows 95     :C:\My Documents
    • Windows 98     :C:\My Documents
    • Windows Me     :C:\My Documents
    • Windows NT     :C:\WINNT\Profiles\ユーザ名
    • Windows 2000   :C:\Documents and Settings\ユーザ名
    • Windows XP     :C:\Documents and Settings\ユーザ名
    • Windows Vista  :C:\Users\ユーザ名
    • Windows 7      :C:\Users\ユーザ名
    • Windows 8      :C:\Users\ユーザ名
  • Unix系
    • Ubuntu(Debian) :/home/ユーザ名
    • Fedora(Red Hat):/home/ユーザ名
    • Max OS X       :/Users/ユーザ名
Windows系のホームディレクトリの表示について、
「¥」と表示されている人と、「\」と表示されている人がいるハズ。
これは利用環境に依存する。以下では「¥」と表示されていることを前提に進める。

さて、上記で「ユーザ名」と書かれている部分はログイン名に対応する。
パソコン購入して最初に起動した時、あるいは、OSを入れなおした際に設定したハズ。
ちなみに、英語名で設定しておいた方がプログラムを書く時には楽。変換の手間が無い。

さて、では、いよいよPython でプログラムを書いていくことにする。
まずは、Pythonコンソールを立ち上げる。「>>>」というのが表示されるのを確認。
基本的な解説は、データ型の話でしているので、以下を理解できない人はそこから。

以下のプログラムを見て、自分で書きなおす場所は解るだろうか?
"ここに自分のホームディレクトリのパスを書く"の部分を自分で書き直す。
この部分、文字列型なので、「'」あるいは「"」で囲むことを忘れずに。

import os

home = "ここに自分のホームディレクトリのパスを書く"

for subdirectory in os.listdir(home):
    print(subdirectory)

OSが提供する機能を用いるので、最初に「import os」で必要なライブラリを読み込み、
次に、home という変数に、自分のホームディレクトリの「パス」を代入する。
その後のループ文(For文)を使って、ホームディレクトリの中身を出力する。

ループ文の「for subdirectory in os.listdir(home):」の部分では、
ホームディレクトリ内のディレクトリ名とファイル名をリスト化し、
ループ処理を繰り返す度にリストの内の一つを subdirectory という変数に代入している。

ループ内で行われている処理は一つだけ。「print(subdirectory)」の部分。
subdirectory という変数に入っているディレクトリあるいはファイル名を表示する。
そして、「os.listdir(home)」に格納されているリストを全て表示したら終了。

ひょっとしたら、非常に長い時間がかかるかもしれない。
途中で止めたいときにはキーボード上で「Ctrl」を押しながら「c」のキーを押す。
すると、処理が強制中断される。ということで、各自、実行!

コンソールにファイル名とディレクトリ名の一覧が出れば成功!
上手くいっただろうか?ここでは、ホームディレクトリを指定し、
そこを「ルートパス」とした「相対パス」で一覧を表示したことになる。

では、今度は、絶対パスでファイルの一覧を取得してみる。

import os

home = "ここに自分のホームディレクトリのパスを書く"

for subdirectory in os.listdir(home):
    fullpath = os.path.join(home, subdirectory)
    print(fullpath)

さて、先程のコードとどの部分が変更されているだろうか?
よく見ると、ループ文の中の処理が一つ追加されている。
「fullpath = os.path.join(home, subdirectory)」の部分である。

「os.path.join(home, subdirectory)」というのは、二つのパスを繋げる機能。
この場合は、変数home(ホームディレクトリ) に格納されているパス名と、
変数subdirectory(ファイル名とディレクトリ名) に格納されているパス名を結合する。

このようにして結合したパス名を新しく定義した「fullpath」に代入し、
「print(fullpath)」の部分で、結果を表示している。
これを実行すると、「絶対パス」でファイル名とディレクトリ名が表示できる。

この方式、面倒かもしれないが、階層が深くなっていくと書く量が少なくて済む。
また、ソフトウェアは実際に何かのファイルやディレクトリを操作する際には、
このように、ルートパスの部分と相対パスの部分を結合し「絶対パス」で指定する。

このことが直感的に解ってしまえば今回の話はこれで終わっても良いのだが…。
折角なので、もう少し、このプログラムの改良を続けてみる。
今度は、ディレクトリ名のみを抽出する。


import os

home = "ここに自分のホームディレクトリのパスを書く"

for subdirectory in os.listdir(home):
    fullpath = os.path.join(home, subdirectory)
    if os.path.isdir(fullpath):
        print(fullpath)

今度は、「if os.path.isdir(fullpath):」という部分が追加され、
「print(fullpath)」の前のインデント(空白)が一つ右に移動している。
ループ文の中に、さらに、別の処理が「入れ子」になって入っている。

さすがに、英語の「if」の意味は解るであろう…まさか…。「もしも」の意。
ここでは、「os.path.isdir(fullpath):」という条件を満たした場合にのみ、
「print(fullpath)」を実行するようになっている。

「os.path.isdir(fullpath):」というのもよく見れば、何をしているか予想できるだろう。
「isdir()」の部分は、「() is directory」にも見える。つまり、「()はディレクトリ」。
要するに、「()」内の文字列がディレクトリの場合に「正しい」と言ってくれる機能

慣れてくると、この手の機能は、ほぼ勘で呼び出すことができるようになる。
プログラミングは、基本的に言語なので、慣れてしまえば簡単。
言語体系を扱うのは文系分野の領域。プログラミング・スキルは文系に向いている。

さて、この処理を実行すると、ディレクトリのみをリストとして表示できる。
使用しているOSとその環境に依存するので一概には言えないが、
最近のOSを使っている人は、「Desktop」というのも含まれているハズ。

マルチユーザ環境において、ホームディレクトリには、あらゆる個人設定が保存される。
職場の共有パソコンなどで、別のユーザがログインすると、
そのユーザのデスクトップが表示されるハズ。他の人のは見えない…普通は。

これは、ユーザごとにホームディレクトリが設定されていて、
ログインするとログイン名に対応したホームディレクトリが読み込まれるのである。
もちろん、通常の設定では、別のユーザのホームディレクトリはロックされて見れない。

さて、今回は、ディレクトリの構造を知るために本格的な(?)プログラムを書いた。
また、「マルチユーザ環境」と「ホームディレクトリ」の話もした。
これらは、普段は意識することが少ないかもしれないが、実は重要。

これらの知識を持っていることは、特に、複数のメンバーで行う調査などで重要。
調査終了後に皆のデータを統合する際にディレクトリを揃えておくと楽なのである。
ただし、これは、メンバー全員がディレクトリの概念を知っていることが前提…。

どこかの偉い大先生が堂々と、「文系はそういった知識は不要!」と言っていた。
そのような時代がかつては「あったのかもしれない」が、今はそんな時代では無い。
少し退屈な話かもしれないが、こういった知識はしっかりと身につける必要がある。

2012/12/16

文系のための「日付型」

我々は、無意識に様々な種類の情報を使い分けている。
例えば、「数値」と「数字」の区別は、何も考えずに出来ている。
人の認識能力は、非常に柔軟に、当たり前のことをこなしてしまうのである。

しかしながら、コンピュータは、人の認識能力にははるかに及ばず、
多種多様な情報の種類を柔軟に分離する能力を持っていない。
人に違和感を感じさせないためには、一つ一つ、定義しなければならない。

そういった定義の中に「データ型」がある。
「データ型」は、非常に多くの種類が存在していて、
このブログで全てを紹介することは不可能に近い。それくらい多い。

本ブログでは、すでに、「文字列型」と「数値型」について整理した。
この二つは、最も基本的なデータ型ではあるが、これだけではない。
ということで、今回は「日付型」と呼ばれる「データ型」に注目したい。

まず、一つの疑問として、なぜ、「日付型」が必要なのか?
一見すると、その型を定義することに重要な意味を見いだせないかもしれない。
しかしながら、この型を定義しなければ少々厄介なことが起きるのである。

「12月31日の『1』日後の日付を教えてください。」という質問にどのように答えるか?
おそらく、普通の人は、即座に「1月1日」と答えるであろう。常識的に。
では、この質問の内容を、データ型の考え方を含めて整理してみるとどうなるか?

「1日後」の日付ということは、これは日付の「足し算」をしていることになる。
したがって、この問い合わせに適したデータ型は文字列型ではなく、数値型であろう。
月と日は、「整数値」で表すことができるので、仮に、そのように定義したとする。

すると、困ったことになっていまう。「31+1=32」になってしまうので、
「12月31日」の次の日が「12月32日」になってしまい、「1月1日」にはならない。
年末の忙しい時期、「32日」があったら嬉しいかもしれないが…システムとしては困る。

また、日付の表し方の問題もある。「○○月××日」という表し方、
「○○-××」、「○○/××」あるいは「××/○○」といった表現方法もある。
これらの問題を解決しないと、コンピュータ上で予定表が作れない。

国や地域、文化圏によって異なる表記さえも考慮しなくてはならない。
要するに、「日付型」というのは、これらの問題を解決するための「データ型」である。
つまり、日付型の主要な役目というのは、次の二つに分けることができる。
  • 日付や時間の演算を可能にする。
  • 要求に応じて適切な日付表記を提供する。
さてさて、かなり前置きが長くなったが、まずは、PostgreSQL 9.1 のデータ型を確認。

型名サイズ説明最遠の過去最遠の未来精度
timestamp8 バイト
日付と時刻両方
(時間帯なし)
4713 BC294276 AD1μ秒、14桁
timestamp8バイト
日付と時刻両方、
時間帯付き
4713 BC294276 AD1μ秒、14桁
date4バイト日付(時刻なし)4713 BC5874897 AD1日
time 8バイト時刻(日付なし)00:00:0024:00:001μ秒、14桁
time12バイト
その日の時刻のみ、
時間帯付き
00:00:00+145924:00:00-14591μ秒、14桁
interval12バイト時間間隔-178000000年178000000年1μ秒、14桁

大きく分けて4種類、全部で6種類の「日付型」が定義されていることが解る。
このデータ型に関しても、表現できる日付の区間には「上限」と「下限」があるので、
この範囲を超えた日付は、これらの型では扱えない。これは、他のデータ型と同じ。

また、「精度」に「14桁」と書いてある型は「浮動小数点型」が使われている。
date型は「整数値型」のようである。いずれも、どこかを基準に「0」とおいていて、
そこからの日数で日付を計算しているらしい。

実は、この「基準となる日」が重要。
PostgreSQL の場合、その基準日は「2000年1月1日 00:00」となっていて、
基本的に、時間に関しては、小数点以下の部分で表すようになっている。

とりあえず、timestamp型を見てみる。これは、日付と時間の両方を表すことができ、
さらに、時間帯(タイムゾーン)の指定できる。
date型とtime型は、timestamp型で表記できる部分を、日付と時間に分けたようなもの。

時間帯というのは、世界協定時間(UTC)からの時差。
例えば、「2012-12-16 12:58:23 -9:00」のように表すことができる。
通常は、timestamp型、date型、time型の3つのいずれかを使うことになろう。

コンピュータ上で日付を扱う際には、気を付けるべきことがいくつかある。
日付を記述する際の、「年」と「月」と「日」の書き方である。
この表記方法は、国や地域によって異なり、間違えると年と日が入れ替わる。

例えば、YMD方式と言われる方法では、Year/Month/Dayの順番で表記し、
一方、MDY方式と言われる方法では、Month/Day/Year の順番で表記する。
例えば、「12/12/10」の場合、どちらかを区別することは可能であろうか?

  • YMD方式:2012年12月10日
  • MDY方式:2010年12月12日

となり、全く異なる日付になってしまう。この問題を回避するのは簡単である。
要するに、年を省略せずに「4桁」で記述する癖をつけておくこと。
かつては、データの容量を節約するために「2桁」表記をしていたが、今は関係無い。

最も安全な記述方法は、年と月と日を「-(ハイフン)」で区切った方法。
例えば、「2012-12-16」と記述する。間違い無く、特定の日付を指定できる。
実は、ISO 8601という国際標準で定められた方式。

少し、話が逸れるが、コンピュータ上で日付を扱う際に「和暦」を用いている人がいる。
確かに、ソフトウェアによっては、これを上手く操作している場合もあるが、
計算プロセスが増えてしまうので良い方法とは言えない。西暦表記に直した方が良い。

これらの問題が、なぜ重要かは、データ型のことが分かっていれば解るハズ。

また、いわゆる文系分野では、歴史を扱う場合もあるが、
それぞれの日付型の「上限」と「下限」についても考慮した方が良い。
 timestamp型、date型、time型の3つの型では、「縄文時代」は扱えない。

そもそも、現在のデータベースシステムは、それほどまでに古い時間を扱う想定が無い。
日付型で古い時代を扱うためには、interval型を拡張すれば良いのかもしれないが、
正直なところ、私は、interval型を上手く使いこなせていない。色々と検証が必要。

さて、今回は、「日付型」について整理した。
最も重要なことは、ある基準となる日があって、そこからの日数で計算すること。
したがって、その実体は数値型であり、timestamp型などでは浮動小数点型を使っている。

ただし、注意しないといけない。

「文字列型」と「数値型」と同様に、システムやソフトウェアによって定義が異なる。
面倒なことに、基準日の設定そのものが異なるので、
自分が使っているシステムやソフトウェアの仕様を確認する必要がある。

特に、Excelは色々と厄介で、知っていないと困ることがある。
基本的には、1900年1月1日が基準であるが、1904年1月2日を基準とする方法もある。
1904年1月2日を基準というのは、何とも半端な気もするが閏年が関係しているらしい。

ちなみに、この方法で表された時間のことを「シリアル時間」と呼ぶ。

Windows版のExcelの場合には、1900年が基本であるが「負の値」を扱えない。
一方、Mac版Excelの場合には、1904年が基本であり「負の値」も扱える。
もちろん、どちらの版も、相互に変換できるが、4年と1日ほどずれる。
(この話、執筆時にUbuntuを使っていたので未検証。)

この問題が理解できていれば、Excelのあの不思議な現象の原因も容易に理解できる。
つまり、ID番号のつもりで「12-12-16」などと入力すると「2012年12月16日」になり、
慌てて、文字列型に変換すると「42716」となる問題…これは1900年1月1日からの日数である。

日付では無いのであれば、日付型に判断されないようにする工夫が必要で、
最も簡単な方法は、先頭に何らかの文字を置いて日付型に見えないようにするか、
あるいは、「"」や「'」で囲んで文字列型にするという手段がある。

ところで、当然のことながら、Python にも日付型が定義されているが、
その基準日は、1970年1月1日 00:00 である。PostgreSQL ともExcelとも異なる…。
ということで、ここからは、Python で実験してみる。

試しに、Python コンソールを開いて、以下の二行を実行してみる。

import time
time.gmtime(0)

ここでは、一行目で「時間」を扱うライブラリ「time」を読み込んで、
二行目では、そのライブラリが持っている「gmtime()」という機能を使っている。
「0」とすることで、基準となる「0」の日時を取得できる。結果は以下の通り。

>>> time.gmtime(0)
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)

この結果から、1970年1月1日 00:00 が基準であることが解る。
また、「tm_wday=3」となっているので、月曜日を「0」として、
この日が「木曜日」であることが解る。「tm_yday」は、365日換算した時の日数。

最後に、tm_isdstと言うのは、年を2桁で表示した時の問題を回避するためのものらしい。
いわゆる「2000年問題(Y2K問題)」の対策のためのもの。
ついでに、以下のコマンドも実行してみる。

time.time()

これは、現在時刻を浮動小数点型で教えてくれる。実行結果は以下の通り。

>>> time.time()
1355635704.049219

これが、基準日からの現在の日付と時刻を表している。
さらに、これを、我々が見慣れた表記にするためには、
最初の「time.gmtime」を使って以下のようにする。

time.gmtime(time.time())

そして、この実行結果は以下の通り。

>>> time.gmtime(time.time())
time.struct_time(tm_year=2012, tm_mon=12, tm_mday=16, tm_hour=5, tm_min=30, tm_sec=7, tm_wday=6, tm_yday=351, tm_isdst=0)

このように、日付型は内部的には数値型で計算され、
必要に応じて、適切な表示がなされるのである。

「日付型」は、我々が、コンピュータ上で頻繁に扱っているにも関わらず、
あまり意識することが無いデータ型であり、意外に、奥が深い。
扱いを間違えると、全く異なる日時を示すことがあるので注意が必要である。

2012/12/06

文系のための「文字列型」(2)

「データ型」というのは、コンピュータを使う上で最も基礎的な知識の一つ。
それにも関わらず、初学者には理解しにくく、本を読んでも理解し難い。
最も良い方法は、実際にプログラムを書いてみること。

このブログの読者の中には、強い抵抗を感じる人も少ないかもしれないが、
プログラミングは、意外にも文系向けの技能。論理的思考ができれば誰でも書ける。
プログラマを目指す必要は無いが、少し書ければ、雑務はかなり減る。

別の仕事が増えても責任を取ることはできないが...。

とは言うものの、このブログの目的は、プログラムの話では無いので、
プログラムの書き方を知らない人のことを考えて可能な限り平易に書くように努める。
比較的簡単なプログラミング言語にPythonというものがある。以下ではこれを用いる。

公式サイトは、http://www.python.org/ 。ここで、ダウンロードできる。
もちろん、無償で入手できる。注意すべきはバージョン。
インストールするべきは、「2.x」。このブログ執筆時には「2.7」。

とにかく「Python 2.7 」をインストール。

「2.x」と「3.x」はかなり違う。間違えると以下の処理ができないかもしれない。
この二つのバージョン、現在でも、同時並行で使われている。
「3.x」の方が最新バージョン、という訳では無いので注意。

Python のインストール方法は、探せば丁寧な説明は多く存在するはず。
Google検索で「Python2.7 インストール方法」などと検索するのが良い。
私が説明するよりも解りやすい説明をしている人がいるはず。

さて、ここから先は、インストールをしていることを前提に話を進める。
もちろん、インストールして無くても良いのだが、
折角の機会でもあるし、まだの人はインストールすることを進める。

以上の準備が完了したら、Python を起動する。
Windows の人は、IDLE(Python GUI) というプログラムをし、
MacとUbuntu の人は、Terminal(端末)上で Python と入力して実行する。

とりあえず、画面上に「>>>」という記号が表示してあって、
その横で「|」が点滅している状態であれば大丈夫。
この状態はコマンドの待機状態。コンピュータに命令できる状態。

この画面を「コンソール画面」と呼ぶ。
では、Pythonにおける「文字列型」の話を続けることにしよう...。

PostgreSQL の文字列型とPythonの文字列型は、当然ながら異なるのであるが、
「データ型」を理解するには、様々な環境下のデータ型を理解しておいた方が良い。
なお、データ型の定義は、PostgreSQL の方が「厳密」。Pythonはかなり「甘い」。

PostgreSQLでは4種類の文字列型が存在したが、Python では1種類のみ。
Python の文字数限界は、半角英数の場合で「1,073,741,824」文字まで格納でき、
日本語など全角文字の場合では「536,870,912」文字まで格納できる。

このような「性能上」の違いはあるが、基本的な処理の仕組みは同じ。
もちろん、文法上の違いはあるが、仕組みを理解できれば今は良い。
Python から PostgreSQL を直接的に操作できるのだから、両方知っておいた方が良い。

さてさて、今回は、初っ端から話が脱線しっぱなしである。話を元に戻そう。
今回の話では、「文字列型」基礎的な性質と簡単な演算について理解する。
実は、数値と同じように「文字列型」にも様々な演算が存在している。

とりえあえず、練習から。コンソールに以下の3行をコピーし、
深く深呼吸してから、Pythonのコンソール画面に貼り付ける。
コピー&ペーストの作業は、マウス操作で行った方が良い。

"Hello"
'Hello'
Hello

すると、以下のような出力が得られるハズ。
何も恐れる必要は無い。別にパソコンが壊れる訳ではない。
日本語のメッセージに置き換わっているかもしれないが同じ内容。

>>> "Hello"
'Hello'
>>> 'Hello'
'Hello'
>>> Hello
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Hello' is not defined

最初の二行は実行した行の下に 'Hello' というのが表示され、
最後の一つには、何やら、怪しげなメッセージが表示されている。
落ち着いて最後の三行を見てみる。

一行目は、このメッセージの直前の処理から何かのエラーが帰ってきたことを示す。
二行目は、このメッセージが直前の処理の何行目でエラーが発生していたことを示す。
三行目は、このメッセージが伝えているエラーの種類を示している。

NameError: name 'Hello' is not defined

要するに「ネームエラー(Name Error)」というのが出ている。
では、「'Hello' という名前は定義されていません」どういう意味か?
実は、「"」あるいは「'」で囲まれていない文字列は「変数」として認識される。

つまり、プログラム上では、「"」あるいは「'」で囲まれている部分が、
「文字列」として扱われているのである。コンピュータ上では非常に重要な記号の一つ。
ちなみに、現段階では「"」と「'」は、どちらを用いても良い。使い分けはいずれ。

さて、この表記方法、Python に限ったことでは無い。
なんと、あのエクセルでも同じ。エクセルにID番号として「12-12-01」のようなものを入れると、
勝手に「2012年12月1日」というように日付に変換されたことは無いだろうか?

これは、エクセルが入力されたデータから自動的にデータ型を推測し、
「どうやら、このデータは日付型らしい」という判断をした結果である。
この問題に頭を抱えている人を見かけることは少なくない。

実は、この問題の回避する手段は非常に簡単である。
「'12-12-01」のように、最初に「クォーテーション」を付けて入力すれば良い。
すると、文字列と認識され、実際には「'12-12-01'」として扱われる。

さて、このことを確認できたら、次に「文字列型」の四則演算から実験してみる。
何も考え無くても良いので、以下の4行をコピーして、コンソール画面に貼り付ける。

"Hello" + "World"
"Hello" - "World"
"Hello" * "World"
"Hello" / "World"

すると、以下のような出力が得られる。エラーも出ている。

>>> "Hello" + "World"
'HelloWorld'
>>> "Hello" - "World"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> "Hello" * "World"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>> "Hello" / "World"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'str'

さて、コピーした4行というのは、"Hello" と "World" の演算である。
要するに、足し算、引き算、掛け算、割り算の3つの計算を一処理ずつ実行している。
さて、この出力を見て気になるのはエラーの部分。

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'

最初の二行はさっきのエラーと同じ。最後のメッセージ行のみが重要となる。
ここで「str」とは、「string」の略で、Pythonの「文字列型」を表すので、
「文字列 」と「文字列」の計算において、対応していない演算(記号)が使われたのがエラーの原因。

さて、ここでクイズ。上記4つの処理の中で、唯一このエラーが発生していないのはどれか?
4行分をコピーして実行したのであるが、1処理ずつ実行していることも重要。

>>> "Hello" + "World"
'HelloWorld'

これが答え。エラーを示す三行のエラーメッセージの代わりに、
"HelloWorld" という文字列が表示されている。
以上のことから、文字列は、足し算することができることが解る。

前回の話でも解説したが、「文字列」というのは「文字」の配列である。
つまり、"Hello" という5文字の配列と、 "World"という5文字の配列を足すことができる。
その結果が "HelloWorld"である。このように考えると他の演算が出来ないことも納得できる。

さて、では、以下のような演算は可能であろうか?同様にコピペ。

"Hello" - 4
"Hello" * 4
"Hello" / 4

この演算結果は以下の通り。

>>> "Hello" - 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'int'
>>> "Hello" * 4
'HelloHelloHelloHello'
>>> "Hello" / 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'

先程と同様に、この中で一つだけエラーが出ていない命令がある。どれか?
答えは、「"Hello" * 4」の計算。これは、"Hello"という配列が4つという意味。
したがって、'HelloHelloHelloHello' という結果が表示されているのである。

さて、ここでも、エラーを確認してみる。見るべき部分は三つ目の行。
今度は「int」というのが出てきている。これは「整数値型」を意味する。
つまり、文字列と整数値の演算において対応していない演算が行われたことを意味する。

以上のことから、文字列同士の演算は、「足し算」のみが可能であり、
また、文字列と整数値の演算は、「掛け算」のみが可能である。

さて、これで「メデタシ、メデタシ」とはいかない。
実は、文字列と整数値の足し算をまだ実験していない。
この処理、ちょっと、変わった結果になるからである。

"Hello" + 4

というのを実行してみると、以下のようなエラーが出る。

TypeError: cannot concatenate 'str' and 'int' objects

よく見ると、エラーメッセージの内容が先ほどと異なる。
「文字列と整数値をくっつけることができません」と言っている。
つまり、演算としては存在するが、整数型を文字列に足すことはできないのである。

そうそう。同じ「数」であっても「数値」と「数字」は異なるのであった。
では、プログラミング上で文字列を認識するには、どうすれば良かったのか?
「"」あるいは「'」で囲めば良かった。ではやってみる。

"Hello" + "4"

これを実行すると以下のようになる。

>>> "Hello" + "4"
'Hello4'

すると、"Hello" という5文字の配列と"4"という1文字の配列の足し算となり、
この二つをくっつけた文字列として'Hello4'が表示されるのである。
このようにして、文字列を結合することができるのである。

別の考え方もできる。先程は、文字列としての「数」を表すという考え方であったが、
整数型を文字列型に変換するという考え方もできるハズ。
このようなデータ型の変換のことを「キャスト」と呼ぶ。

"Hello" + str(4)

このようにすると、4という数値は、strという文字列型に変換される。

>>> "Hello" + str(4)
'Hello4'

もちろん、結果は先程と同じ'Hello4'である。
なるほど、段々と状況が読めてきた。では、最後に、以下の演算を実験してみる。
以下の3つの計算、実行前に計算結果を予測できるだろいうか?

256 + 256
"256" + "256"
str(128) * 3

では、答え合わせ。実行してみると以下の通りになる。

>>> 256 + 256
512
>>> "256" + "256"
'256256'
>>> str(128) * 3
'128128128'

さて、予想通りの結果になったであろうか?

コンピュータ上では、数値と数字は異なるということと、
文字列の足し算と掛け算が配列に対して行われているということ、
この二つのことが理解できていれば、簡単に予測できたことであろう。

自分のための「Pythonの文字数限界」

文系のための「文字列型」(1)を書いていて思ったことがある。
Python における文字列の「文字数限界」はいくつかという問題?
よく考えてみると、実際に確かめたことが無かった。

仕様にもちゃんと書いていない(?)。ということで実験。
1バイト文字の場合と2バイト文字の場合も検討してみる。
限界に達するほどの文字を扱ったことは無かったが考えてみると重要。

結論から言うと、半角英数の場合で「2^30=1,073,741,824」文字までいける。
日本語など全角文字の場合では「2^29=536,870,912」文字までいける。
これが十分かどうかは、何とも言えないが、ちょっとスッキリ。

以下が、ソースコード。実験1と実験2の二つのソースがある。

実験1:半角英数(1バイト文字)の場合

# 文字数「n」の初期値を設定する。
# 1文字から始めるので「n = 1」から。
n = 1

# エラーが出るまでひたすら実行する。
while True:
    # 変数「c」に文字「a」を「n」個入力する
    c = "a" * n
    # この時の「n」を表示する
    print n
    # 変数「a」に入力されている値を初期化する
    del c
    # 「n」を2倍して繰り返し処理を行う。
    n = n*2

実験2:全角文字(2バイト文字)の場合

# 文字数「n」の初期値を設定する。
# 1文字から始めるので「n = 1」から。
n = 1

# エラーが出るまでひたすら実行する。
while True:
    # 変数「c」に文字「a」を「n」個入力する
    c = "あ" * n
    # この時の「n」を表示する
    print n
    # 変数「a」に入力されている値を初期化する
    del c
    # 「n」を2倍して繰り返し処理を行う。
    n = n*2

実験1:半角英数(1バイト文字)の結果

1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
OverflowError: cannot fit 'long' into an index-sized integer


実験2:全角文字(2バイト文字)の場合


1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
OverflowError: repeated string is too long



文系のための「文字列型」(1)

コンピュータで扱う情報は「1」と「0」。そのようなデータをデジタルデータと呼ぶ。
デジタルデータには、様々な「型」があって、その「型」に応じた処理が行われる。
ここまでの話を整理すると、ざっと、こんな感じである。

さて、今回は、様々な「データ型」の中で、利用頻度が高い「文字列型」について。
おそらく、一般的なユーザが最も頻繁に用いている「型」かもしれない。
もちろん、現在、書いているブログの文章も「文字列型」で構成されている。

まずは、「文字列型」とは何かについて考えてみる。

すでに、述べたように、コンピュータ上で扱う「文字」は、
たとえ数字であったとしても、何らかの「対応表」を参照していて、
その対応表の場所を「2進数」で示しているのであった。

こうして対応表を通して「2進数」で記述することを「符号化」と呼び、
「符号化」された1文字を「文字型」、これを「配列」にしたものを「文字列型」と呼ぶ。
プログラミング言語の中には、「文字型」しか持たないものもある。

さて、今回は「文字列」の限界、すなわち、「文字数限界」について考えてみる。
実は、コンピュータで扱える「文字数」には限界がある。
もちろん、エクセルの一つのセルに入力できる文字数にも限界がある。

このことは、データベースを自分で構築する際の最も重要な知識の一つであり、
また、エクセルなどで比較的長い文字を扱う人は理解しておいた方が良い。
特に、文献資料等の文字資料を扱う人は注意すべきで問題である。

では、早速、PostgreSQL9.1における「文字列型」を見てみる。
  1. character あるいは char
  2. character(n) あるいは char(n)
  3. character varying(n) あるいは varchar(n)
  4. text
実は、PostgreSQL 9.1 では、以上の4つの「文字列型」が存在している。

一つ目は、いわゆる「文字型」つまり、1文字だけ格納可能な型。
二つ目は「固定長文字列型」、三つ目は、「可変長文字列型」であり、
そして、四つ目は「テキスト型」と呼ばれている。

見方が解らない人が居るかもしれない。ということで、まずは、表示方法について。
括弧が付いている場合は、「()」の所までが「型名」で、
「()」の中が「引数(パラメータ)」であり、この型の「限界値」である。

「限界」というのは、この文字列の配列の数を表していて、
「n」には、配列の長さをあらわす「1つの整数値」が入る。
つまり、「n」は、「文字数の限界」を決めるための「変数」である。

また、「可変長」と「固定長」というのは、この「文字数の限界」に関係していて、
「可変長」の場合は、この「型」に入っている文字列の長さが文字の数に依存し、
「固定長」の場合は、この「型」に入っている文字列の長さはその限界の数である。

要するに、「可変長型文字列」と「固定長文字列型」は文字数を決めないといけない。
一方、「character」と「text」には、「()」が付いていないので、
この型については、引数が不要であることを示している。

例えば、「人名」を「10文字(20bite)」の「文字列型」で指定し、
この型に対応する値として「新島 襄」という値を入力するとする。
この時、それぞれの型ではどのようになるか?

・文字型の場合  :"新"
・可変長の場合  :"新島□襄"
・固定長の場合  :"新島□襄□□□□□□"
・テキスト型の場合:"新島□襄"

「文字型」の場合、「1文字」しか受け付けないので、「新」以降は切り捨てられる。
「可変長」と「テキスト型」の場合は、同じで、入力した通りに値が入る。
そして、「固定長」の場合には、10文字になるまで「空白(□)」で埋める。

もしも、10文字以上の名前が存在したらどうなるのか?
これも考えてみる。もっと長い名前と言えば...「寿限無」?
といことで、寿限無を入力してみる。

・文字型の場合  :"じ"
・可変長の場合  :"じゅげむ□じゅげむ□"
・固定長の場合  :"じゅげむ□じゅげむ□"
・テキスト型の場合:"じゅげむ□じゅげむ□ごこうのすりきれ
          かいじゃりすいぎょの□すいぎょうまつ
          うんらいまつ□ふうらいまつくうねると
          ころに□すむところ□やぶらこうじの□ぶ
          らこうじ□ぱいぽぱいぽぱいぽのしゅー
          りんがん□しゅーりんがんのぐーりんだ
          い□ぐーりんだいのぽんぽこぴーのぽん
          ぽこなーのちょうきゅうめいのちょうす
          け"

このようになる。「テキスト型」のみは全ての文字を格納できるが、
「文字型」は1文字のみ、「可変長」と「固定長」はともに10文字の限界があるので、
超過した部分は切り捨てられる。

通常は意識したことは無いであろうが、
実は、このような「文字数の制限」というのは、
データベースに限らず、様々な所で出てくる問題である。

なぜ、このように複雑な定義が必要なのか?

最近のコンピュータは、性能が高いので意識することは「少ない」が、
コンピュータのメモリやCPUの負担を少なくするための工夫が現れているのである。
ちょっと大きなデータを扱うと、最新のコンピュータでも処理が追いつかないのである。

ちなみに、少し前までは、引数「n」に指定できる限界値は非常に小さく、
「128文字(bite)」あるいは、もう少し大きくて、「256文字(bite)」であった。
かつては、限られた資源を上手く使い回すのが腕の見せ所だった。

なお、PostgreSQL 9.1 では、最大で1GBまでサポートされている。
単純に計算すると、2バイト文字で非圧縮の場合4096文字程度まで格納できる計算である。
これよりも長い「文字列」を扱う時には、ちゃんと、仕様書を確認した方が良い。

実際には、文字列を圧縮するようなので、もう少し格納できるかもしれない。
「文字数限界」については、深く知ろうとすると色々と厄介なのでここでは割愛。
いずれにせよ、長大な「文字列」を扱う際には注意が必要である。

次回は、Python というプログラミング言語を通して、
もう少し、文字列型の性質や操作について考えてみる。

文系のための「データ型」

これまでの話から、コンピュータは、2進数と呼ばれる「1」と「0」の値によって、
データを扱っていることは理解できているハズ。
ONとOFFのスイッチの組合せで動いているとも言える。

また、重要なこととして、同じ2進数による表現であっても、
対応表を参照する方法と、10進数を2進数変換した数値で扱う方法の、
二種類方法が存在していることも理解できているはず。

例えば、ASCII文字コードに代表される「文字」は、対応表による方法であり、
色の場合は、8bitの場合には、256段階の色の強さを2進数の数値で表した方法であった。
もちろん、通常の計算は、2進数の計算で対応できるのであった。

そうそう。同じ「数」であっても、「数字」と「数値」は別のもの。
見た目が同じでも、コンピュータは、別のものとして扱うのだった。
では、こういったことを知っていないと、どのようなことで困るか?

エクセルなどの表計算ソフトを使っていて、
次ような問題に出会ったことは無いだろうか?

ID番号と思っていたものが勝手に日付になったり、
足し算しないといけないにも関わらず、なぜか、計算できていなかったり...。
あるいは、小数点以下の数値が勝手に四捨五入されていたり、ということもある。

知っていれば、こういった問題に頭を抱えることが激減する。自力で解決できる。

コンピュータのデータの扱い型を知ることは、ある居程度は重要なことである。
「文系」とか「理系」とかではなく、無用な仕事を増やさぬためにも。
実際に、こうした問題で相談を受けることは少なくない。いや、多い。

もちろん、これらの質問に対して、不快に感じたことは無いが、
現実問題として、文系であってもコンピュータで困ることはある。
「文系だからそこまで知らなくてもね〜」と言ったのは誰だったか...。

「文系は使えれば良い派」には断固として反対。

さて、そんな愚痴っぽい批判のようなことはさておき、
コンピュータの仕組みをある程度知っておいて損は無い。
応用も利くし、複雑な処理が、簡単な処理で済むことが極めて多い。

理解しておくべきことは、人が目で見て理解していることと、
コンピュータが理解できることは全く異なるということ。それゆえに、
人が当たり前にできるようなことであっても、コンピュータが出来ないことも多い。

日付の計算は、その代表的な例と言える。「12月31日」の次の日は?
普通の人は、「1月1日」と答えるであろう。常識である。
しかしながら、うっかりすると、コンピュータは「12月32日」と答える。

コンピュータは、それほど賢いものでは無い。

では、人が当たり前のように行なっているような、
非常に複雑な手続きを扱うために、どのような仕組みを持っているのか?
というのが今回の話のテーマ。

キーワードは「データ型」。

一般的に、「データ型」と呼ばれる存在は、
この問題を解決するために生み出された「苦肉の策」的仕組みである。
この仕組みは、理想的とは言えないものの、現在の唯一の手段である。

簡単に言うと、人がコンピュータに何らかの命令を与える時に、
扱うべきデータが、文字なのか数値なのかを教えてやる、という方法。
文字と数値だけではな無い。日付、時間、図形といった「型」を教えてやる。

コンピュータに様々な「型」の種類と、その「型」特有の処理方法を与えておけば、
コンピュータはその通りにデータを処理してくれる。
人が、コンピュータに合せてやるということが必要ではあるが...。

さて、この話をスムーズに理解できた人にとっては「それで?」と思うかもしれない。
「一つずつそのデータ型とやらの説明してくれれば良いではないか?」とも思うかも。
実を言うと、それほど単純ではない。例えば、こうした「型」は環境によって定義が異なる。

Microsoft Excel という馴染み深い表計算ソフトが扱う「データ型」と、
PostgreSQL などのデータベース管理システムが扱う「データ型」、
C言語よ呼ばれるプログラミングが扱う「データ型」は、それぞれ、定義が異なる。

さらに、同じMicrosoft Excel であっても、「データ型」に関して、
なぜか、Windows版とMac版で微妙に異なる部分があるなど、
一つでは説明しきれないことも色々とある。

同じデータベース管理システムであっても同様。PostgreSQL、Oracle、
DB2、SQL Server、SQLite、Access といった商品毎によって、
「データ型」の定義は、多かれ少なかれ、違いがある。

もちろん、プログラミング言語も。C言語、PL/I、Delphi、COBOL、Python、Ruby、
などなど、これら意外にも、数えきれないほどプログラミング言語は存在するが、
プログラミング言語によって、「データ型」の定義が異なる。

さて、この問題は、「データ型」についての知識を「教える側」としては、
非常に悩み深い問題でなのである。すなわち、何をベースにするべきか...。
どれを選んでも、多少の矛盾が出てきてしまう。

ということで、悩みに悩んだ末、PostgeSQLとPythonの「データ型」をベースに、
以下の話を進めていくことにする。この二つを対照させながら、
データ型について整理することにする。

ただし、PostgreSQL のデータ型は非常に多いので、全ては説明せず、
一般論として関係しそうな基本的なデータ型に重点を置いて整理することにする。

なお、以下の表は、PostgreSQL 9.1 のデータ型の一覧である。
赤字の所が、現在、説明を予定しているデータ型。
名称別名説明
bigintint88バイト符号付き整数
bigserialserial8自動増分8バイト整数
bit [ (n) ]固定長ビット列
bit varying [ (n) ]varbit可変長ビット列
booleanbool論理値(真/偽)
box平面上の矩形
byteaバイナリデータ("バイトの配列(byte array)")
character varying [ (n) ]varchar [ (n) ]可変長文字列
character [ (n) ]char [ (n) ]固定長文字列
cidrIPv4もしくはIPv6ネットワークアドレス
circle平面上の円
date暦の日付(年月日)
double precisionfloat8倍精度浮動小数点(8バイト)
inetIPv4もしくはIPv6ホストアドレス
integerint, int44バイト符号付き整数
interval [ fields ] [ (p) ]時間間隔
line平面上の無限直線
lseg平面上の線分
macaddrMAC(メディアアクセスコントロール)アドレス
money貨幣金額
numeric [ (p, s) ]decimal [ (p, s) ]精度の選択可能な高精度数値
path平面上の幾何学的経路
point平面上の幾何学的点
polygon平面上の閉じた幾何学的経路
realfloat4単精度浮動小数点(4バイト)
smallintint22バイト符号付き整数
serialserial4自動増分4バイト整数
text可変長文字列
time [ (p) ] [ without time zone ]時刻(時間帯なし)
time [ (p) ] with time zonetimetz時間帯付き時刻
timestamp [ (p) ] [ without time zone ]日付と時刻(時間帯なし)
timestamp [ (p) ] with time zonetimestamptz時間帯付き日付と時刻
tsqueryテキスト検索問い合わせ
tsvectorテキスト検索文書
txid_snapshotユーザレベルのトランザクションIDスナップショット
uuid汎用一意識別子
xmlXMLデータ


2012/11/26

自分のための「LinuxでOCR」

Linux でも利用できるOCRライブラリに tesseract-ocr というのがある。
詳しい説明は省略。英語環境であればかなり快適。
場合によっては、商用のものよりも精度も良い。

このライブラリ、基本的にはCUIで動作するので、
バッチ処理をしたい場合に便利。今回は、Pythonで書いてみた。
比較的、快適に動いたので、成功例としてUP。

以下、説明に迫られて書いた極めて「テキトー」なコード。
今回、本当にメモ。動く保証は無い。使う人はいるかもしれない。
まぁ、いつもどおりのことかもしれないが...。

以下からソースコードをダウンロードすることができる。
https://docs.google.com/open?id=0B9OtIs5BjRVha21YQXlHSGZUTWc

使い方は簡単。コンソール(端末)上で、以下のように入力する。
Pythonパスを通して居ない場合には、"cis.ocr.py" もフルパスで。
引数「-d」の後に、OCRに掛けたい画像が入ったディレクトリを指定する。

python cis.ocr.py -d 'path/to/image/directory'

実際のコードを見て見て解るように、
以下のコードではビットマップ画像のみサポートしている。
修正すれば、JPEGやTIFFにも対応可能。その場合は拡張子の判定部分を修正。

ふむ...それにしても、このコードはコピペ感が強い。
もちろん、コピペ元は自分のコードであるが。
次に見直す時には、もう少し、コードを整理してみるか。

#!/usr/bin/python
# -*- coding: utf-8
# Import modules
import subprocess, string, shlex, os, Image, numpy, tarfile, math, nltk, shutil, sys, getopt, zipfile
import xml.etree.ElementTree as et
from sets import Set
from progressbar import *

def txtCreansing(file):
    '''
    Cleansing OCR text. Converting some unreadable characters into readable characters.
    @param file: File path for cleansing.
    @type file: C{string}
    @returns: Cleaned text with string type.
    @rtype: C{string}
    '''
    text = ""
    text = text + open(file).read().translate(None, "-\n\\")
    text = text.replace("\xe2\x80\x90", "-")
    text = text.replace("\xe2\x80\x91", "-")
    text = text.replace("\xe2\x80\x92", "-")
    text = text.replace("\xe2\x80\x93", "-")
    text = text.replace("\xe2\x80\x94", "-")
    text = text.replace("\xe2\x80\x95", "-")
    text = text.replace("\xe2\x80\x96", "ll")
    text = text.replace("\xe2\x80\x98", "'")
    text = text.replace("\xe2\x80\x99", "'")
    text = text.replace("\xe2\x80\x9a", ",")
    text = text.replace("\xe2\x80\x9b", "'")
    text = text.replace("\xe2\x80\x9c", '"')
    text = text.replace("\xe2\x80\x9d", '"')
    text = text.replace("\xe2\x80\x9e", ',,')
    text = text.replace("\xe2\x80\x9f", '"')
    text = text.replace("\xe2\x80\x93", "-")
    text = text.replace("\xe2\x80\x94", "--")
    text = text.replace("\xe2\x80\xa4", ".")
    text = text.replace("\xe2\x80\xa5", "..")
    text = text.replace("\xe2\x80\xa6", "...")
    text = text.replace("\xef\xac\x81", "fi")
    text = text.replace("\xef\xac\x82", "fl")
    text = text.replace("\xef\xac\x83", "ffi")
    text = text.replace("\xef\xac\x84", "ffl")
    text = text.replace("\xef\xac\x85", "ft")
    text = text.replace("\xef\xac\x86", "st")
    text = text.replace("\xef\xac\x9d", "!")
    text = text.replace("\xef\xac\x9f", "!!")
    text = text.replace("\xc3\xa9", "e")
    text = text.replace("\xc2\xb0", "'")
    text = text.replace("\xc2\xa2", "c")
    test = text.replace("``", '"')
    text = text.replace(".", " . ")
    text = text.replace(",", ", ")
    text = text.replace("  ", " ")
    text = text.replace("'r", "t")
    text = text.replace("'", "\\'")
    text = text.replace('"', '\\"')
    return text

def getTextFromImage(path):
    '''
    OCR by using tesseract-OCR.
    '''
    if os.path.exists(path + ".txt") == False:
        args = "tesseract '%s' '%s'" % (path, path)
        subprocess.call(shlex.split(args),shell=False)
        text = txtCreansing(path + ".txt")
    else:
        text = txtCreansing(path + ".txt")
    return text

def main(argv):
    indir = ''
    if len(argv) == 0:
        print 'Input directory is not specified:'
        print 'cis.ocr.py -d <input directory>'
        sys.exit(2)
    try:
        opts, args = getopt.getopt(argv,"hd:",["idir="])
        if len(opts) == 0:
            print 'Input directory is not specified:'
            print 'cis.ocr.py -d <input directory>'
            sys.exit(2)
    except getopt.GetoptError:
        print 'cis.ocr.py -d <input directory>'
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print 'cis.ocr.py -d <input directory>'
            sys.exit()
        elif opt in ("-d", "--idir"):
            indir = arg
           
    for child in os.listdir(indir):
        # Get a full path of the directories.
        fullpath = os.path.join(indir, child)
        filename = os.path.splitext(fullpath)[0]
        extension = os.path.splitext(fullpath)[1].lower()
        if os.path.isdir(fullpath):
            #print(fullpath)
            getXmlDir(fullpath)
        elif extension == ".bmp":
            getTextFromImage(fullpath)

if __name__ == "__main__":
    # Path to an original xml file object.
    main(sys.argv[1:])