chardetで文字コードを自動変換する

curlなどで取得したテキストを自動的にターミナルの文字コード(たとえばUTF-8)に変換したい場合がある。 このような場合には、Pythonchardetモジュールが使える。 chardetは、Mozilla Firefoxで使われている文字コード判定アルゴリズムPythonモジュールとして移植したものである。

chardetのインストール

DebianUbuntuでは最初からインストールされている。

$ sudo apt-get install python-chardet
Reading package lists... Done
Building dependency tree
Reading state information... Done
python-chardet is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 35 not upgraded.

文字コードを自動判定し、デコードする

次のようなPythonコードを書き、test.pyとして保存する。

import sys
import chardet

text = sys.stdin.read()
result = chardet.detect(text)
print result
print text.decode(result["encoding"])

iconvコマンドでShift_JISに変換したテキストをスクリプトに与えると、次のようになる。

$ echo -n 日本語 | iconv -f utf-8 -t shift_jis | python test.py
{'confidence': 0.99, 'encoding': 'SHIFT_JIS'}
日本語

Shift_JISと判定され、デコードに成功している。

シェル関数にしてみる

次のような関数を.bashrcなどに書くことで、シェル関数として使うことができる。

cats() {
    local CODE
    read -r -d '' CODE <<"__EOF__"
import sys, locale, chardet

text = sys.stdin.read()
locale = locale.getdefaultlocale() or ('en_US', 'UTF-8')
result = chardet.detect(text)
sys.stdout.write(text.decode(result['encoding'], 'replace').encode(locale[1]))
__EOF__
    cat "$@" | python -c "$CODE"
}

ためしに、GB2312で書かれている騰訊QQのHTMLテキストを取得してみる。

$ echo $LANG
en_US.UTF-8

$ curl -s http://www.qq.com/ | head
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta content="text/html; charset=gb2312" http-equiv="Content-Type">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>��Ѷ��ҳ</title>
<script type="text/javascript">
if(window.location.toString().indexOf('pref=padindex') != -1){
}else{
        if(/AppleWebKit.*Mobile/i.test(navigator.userAgent) || (/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/.test(navigator.userAgent))){

HTMLテキストがGB2312で書かれているのに対し、ターミナルの文字コードUTF-8であるため、title要素の中身が文字化けしていることがわかる。

ここで、定義したシェル関数にパイプで繋いでみる。

$ curl -s http://www.qq.com/ | head | cats
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta content="text/html; charset=gb2312" http-equiv="Content-Type">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>腾讯首页</title>
<script type="text/javascript">
if(window.location.toString().indexOf('pref=padindex') != -1){
}else{
        if(/AppleWebKit.*Mobile/i.test(navigator.userAgent) || (/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/.test(navigator.userAgent))){

文字コード判定に成功し、ターミナルの文字コードで表示することができた。

リストの処理に関するトリッキーな記法いろいろ

よく忘れるのでメモ。

一定のサイズごとに分ける

>>> a = range(20)
>>> n = 5
>>> zip(*[iter(a)]*n)
[(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)]

個数が割り切れない場合は、切り捨てられるので注意。

>>> a = range(22)
>>> zip(*[iter(a)]*n)
[(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19)]

切り捨てないで分けるには、やや煩雑ではあるが次のようにする。

>>> [a[n*i:n*(i+1)] for i in xrange(len(a)/n+1)]
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21]]
>>> map(lambda *x: filter(lambda x: x is not None, x), *[iter(a)]*n)
[(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17, 18, 19), (20, 21)]

2次元配列の転置

>>> a = [range(4) for i in range(4)]
>>> a
[[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]
>>> map(list, zip(*a))
[[0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]

個数が合わない場合は、やはり切り捨てられるので注意。

>>> a = [range(2), range(3), range(4)]
>>> a
[[0, 1], [0, 1, 2], [0, 1, 2, 3]]
>>> map(list, zip(*a))
[[0, 0, 0], [1, 1, 1]]

2次元配列の1次元化

sum()は第2引数に初期値を与えることができ、明示的に空リストを指定することでサブリストの結合ができる。

>>> a = [range(4) for i in range(4)]
>>> a
[[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]
>>> sum(a, [])
[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]

あるいは、itertools.chain()を使う。

>>> import itertools
>>> list(itertools.chain(*a))
[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]

最も長い文字列を選ぶ

list.sort()sorted()と同様に、max()もキーワード引数として比較キーを計算する関数を与えることができ、len()を指定することで文字列長などが最も長い要素を1個選ぶことができる。

>>> a = "hello nice to meet you".split()
>>> a
['hello', 'nice', 'to', 'meet', 'you']
>>> max(a, key=len)
'hello'

min()についても同じである。

>>> min(a, key=len)
'to'

二つの配列の直積

あまりトリッキーではないけどついでに。

>>> a = range(2)
>>> b = range(3)
>>> [(i,j) for i in a for j in b]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

あるいは、itertools.product()を使う。

>>> import itertools
>>> list(itertools.product(a,b))
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

PEP 8 をつぶやくTwitter Botを作った

Pythonの標準コーディング規約 PEP 8 に書かれているフレーズを一定間隔でツイートする。

PEP 8、一度読んでもすぐ内容を忘れてしまうのだけど、いろいろ参考になることが書いてあるし、なんとなく眺めていればしだいにPEP 8に準拠したコードが書けるようになる、かもしれない、と思って作った。

追記 (2014/01/09)

アカウント名を変えた。

PyCon APAC 2013 に参加した

野良Pythonエンジニアとしては行くしかない、ということで初参加。 プライベートで行くカンファレンスなので、自由に聴講レポートが書ける。すばらしい。

資料や動画は、タイムテーブルのページから確認できる。 特に動画は会期中にリアルタイムで追加されていっていて、すごいと思った。

Keynote: Python and Neutrons -- or, how to make it fun to move motors (by Georg Brandl)

Python 3.2, 3.3のリリースマネージャで、Sphinxの作者でもある方による基調講演。 主な内容は、Python 3.3の新機能と、ドイツの研究用原子炉 FRM II で使われている Network Instrument Control System (NICOS) の設計・実装について。

Python 3.3の新機能については、generatorの中で他のgeneratorの値を返せるようにする(移譲できるようにする)yield from文の話が興味深い。 下の記事が参考になる。

NICOSについては、ユーザからのさまざまな要望(「実行中にコードを動的に書き換えたい」など)をどう解決しているかの話が面白かった。 のだけど、主に自分の英語力の低さにより、若干理解が追い付かない感じでつらかった。

パッケージングの今と未来 (by @aodag)

今なお混沌を極めるパッケージングについての話。 こういった形でまとめられることそれ自体が有意義といった感じで、ためになる話だった。 途中にPEPベースでの仕様策定の功罪みたいなものにも触れられていたけど、この辺の話はいい感じにまとまっていってほしいなと思う。

Test failed, then... (by torufurukwa)

プログラムも人も疎結合にして、コミュニケーションコストを減らそうという話の実践例。 デバッグ用のDB CRUD APIを定義したり、requestsにcurl形式のURLをデバッグ出力するようラッパーを噛ませたりといった例がへーって感じだった。

Fabric for fun and profit (by jairtrejo)

デプロイツール Fabric の実践的な使用例についての話。 FabricやVagrantをそもそも使ってない状態で聞いたため、コード多めな内容と英語力の低さでいまいちついていけず。 無念。

VFX業界におけるPython活用事例 (by 齊藤弘)

タイトルの通り。人間の3DCGがGUIで作られていくデモがおもしろい。 アプリの拡張は独自スクリプトが多かったが、最近主力級ソフトがPythonをサポートするようになってきたとのこと。 この分野に詳しくないのであまり細かくは書けないが、期待していたものが見れてよかった。

Graphillion: Python module for very large sets of graphs (by Takeru Inoue)

大規模グラフ集合に対する各種演算を扱うPythonモジュール graphillion の話。 Pythonの流儀にのっとったインタフェース、C extension使用で高速、ランダムイテレータなど複雑な処理にも対応といった感じで、かなり実用的っぽい。 質疑応答がプレゼンの作法・ルールみたいな突っ込みで混沌を極めていて、ちょっと講演者の方が気の毒だった。

Deployment, Configuration Management and more with Salt Stack (by izquierdo)

Ansible と双璧をなすPython製構成管理ツール(だと個人的に思っている)Salt Stack についての話。 Saltは1対nのサーバ・クライアント構成をとり、それぞれMaster、Minionと呼ばれる。 また(chef-soloでない)Chefと同様に、クライアント側であるMinionにもエージェントのインストールが必要となる。 なお、Ansibleの場合はクライアント側にエージェントのインストールは不要(sshdが利用される)。

Saltは元々高速なリモートコマンド実行ツールとして作られたとのことで、この点にAnsibleとの違いがある。 たとえば、下記のようにすることで、複数台のサーバで同じコマンドを実行し結果を集約できる。

# salt 'app*.example.com' cmd.run ls /tmp/

app01.example.com:
    hello.txt
    pycon.html
app02.example.com:
    myapp.py
    somethingelse.py

ここでは、cmdモジュールのrunコマンドを使っているが、この部分は拡張可能で brew, iptables, virtualenv などさまざまなモジュールが用意されているとのこと。

日本ではSalt Stackの情報はあまり見かけることがないのだけど、上記の機能も含めて一度使ってみたいと思った。

about mock (by podhmo)

mockモジュールの概要、かと思いきや、mockの機能がどのように実装されているかについての「実践メタプログラミングPython」とでもいえる内容。 全体的に丁寧に追っていけばわかる話だとは思うのだけど、Pythonのメタプログラミング機構をよく知らないため、流れに取り残されてしまった。

後半は、mockを使うことで生じるテストのfalse positive(テストは通っているのに実際はエラーで落ちる)をなくすためのアプローチの話。 アクセスできる属性を制限したり、引数の数をチェックしたりという内容で、セキュリティ技術っぽくて一人テンションが高まる感じだった。

Keynote: One Million Lines of Python (by Rian Hunter)

ここからは台風が到来した2日目の内容。 KeynoteはDropboxの3番目のエンジニアによる、PythonのGood parts / Bad partsの話。 マルチスレッドの話や実行速度の話などいろいろあったが、個人的にはダックタイピングの話、特に「type()/isinstance() ではなく hasattr() を使え」という話がなるほどといった感じだった。 そういう話自体はされているのだろうけど、個人的にPythonでダックタイピングの話を聞いたのはこれが初めてだったので新鮮だった。

PofEAA in SQLAlchemy (by methane)

Pythonの定番ORM SQLAlchemy の設計が、PofEAA (Catalog of Patterns of Enterprise Application Architecture) の設計パターンとどのように対応しているかについての話。 PofEAAも読んでなければSQLAlchemyもきちんと使いこなせてない自分には、得るものが多い内容だった。

不正確であることを前提として、軽く内容をメモしておくと、

  • Unit of Work
    • 一連の操作の責任を持つオブジェクトを作る。途中で部分的な変更が起こった場合は、これまでの操作を踏まえて全体の整合性を取る。SQLAlchemyではSessionとして実装されている。
  • Single / Class / Concrete Table Inheritance
    • 継承関係にあるテーブルをどのように扱うか。一つのクラスで表す(カラム値で区別させる)、親のカラム値をもとに子のクラスを参照する、親のカラムを持たせた子のクラスのみを作る。
  • Data Source Architectural Patterns
    • クラスとDBアクセスをどう対応させるか
    • Table Data Gateway
      • クラスをテーブルに対する処理の集まりとして対応させる
    • Row Data Gateway
      • クラスをテーブルの各レコードに対する処理の集まりとして対応させる。インスタンスがテーブルの各レコードに対応するので、user.delete()がそのレコードを消す操作に対応する。
    • Active Record
      • Row Data Gatewayにドメインロジックも持たせる。user.get_fullname()で姓名を繋げて返す、など。
    • Data Mapper
      • DBアクセスを行うクラスとドメインロジックを表すクラスをそれぞれ作り、間を繋ぐクラスで対応させる

最後には私見として、複雑なクラスについてはシンプルなActive Recordをベースに、会員登録はAccountServiceクラス、フレンド申請はFriendServiceクラスといったようにServiceという概念で分離するのがよいのではないかという話もあり、なるほどといった感じだった。

あとは、Twitterで参加者から挙げられていた下記サイトが参考になりそう。

Python 2.5 から 3.3 で動作するツールの作り方 (by Takayuki Shimizukawa)

タイトルの通り。 Python 2.5のサポートはかなり大変。 sixというPython 2, 3の互換レイヤモジュールがあり、便利とのこと (six = 2 * 3)。 聞いている中で、__future__モジュールにはwith_statement以外にもprint_functionなどいろいろあることに気づいたりした。

NodeBox で始めるジェネラティブ・アート (by Shinichi Morimoto)

Processingのようなアートプログラミングツールの一種 NodeBox と、生成に使われるアルゴリズム (Parlin Noise, Boids) の話。 NodeBoxにはマルチプラットフォームなVersion 3があるが、これはGUIでプログラミングするタイプのものでPythonは拡張言語という扱い。 Version 1はMacオンリーだが、Pythonで書ける。

アルゴリズムの話は軽く聞いたことがあったのだけど、実際に動かしているデモが見れたのはおもしろかった。 最後には、ジェネラティブ・アートの実例集として、http://abandonedart.org/ が挙げられていた。 JavaScriptで動くものが見れておもしろい。

MoSQL: More than SQL, but Less than ORM (by Mosky)

SQLベタ書きとORMの中間的な立ち位置となる、SQL文を生成するライブラリ MoSQL についての話。 ORMではないのでDB接続はふつうのドライバを使うことになるのだけど、インタフェースがPython for Humansな感じでかなりよい、というか、すごくよい。 ドキュメントから引用すると下のような感じ。

>>> print select('person', {'person_id': 'mosky'})
SELECT * FROM "person" WHERE "person_id" = 'mosky'

>>> print insert('person', {'person_id': 'mosky', 'name': 'Mosky Liu'})
INSERT INTO "person" ("person_id", "name") VALUES ('mosky', 'Mosky Liu')

(どうでもいいけどドキュメントの例示コードに自分の名前使いまくってるのすごい)

SQLインジェクションにはエスケープで対策(バインド機構を使わない)というあたりが不安なんだけど、sqlmapでテスト済みということである程度は考えて作られているんだろうな……と思っていたら、質疑応答でバインド機構に対応したSQL文も生成できるとのこと。

あと、後半はターミナルでデモだったのだけど、Serious Usageとして紹介されたコードが、context managerを使ってトランザクション管理してたり、部分適用した関数をインスタンス変数に持たせて利用してたりで、洗練された感じだった。 Python trainerをやってるとのことで、なるほどと思う。 下のような資料もあるみたいなので、そのうち読んでみたい。

bpmappers の紹介 (by 岡野 真也)

オブジェクトの属性から辞書(JSON)へのマッピングを行うライブラリ bpmappers の紹介。 最初はわからなかったけど、社内で広く使われている的な話もあったので、"bp" は社名 (Be Proud) の略なのだと思う。

複数のオブジェクトから辞書を作ることはもちろん、メソッドの戻り値を組み込んだりなど柔軟性は高く、設計も洗練されている。 今はあまり使う機会がない、というかそもそも適用場面が限られる類のものではあると思うけど、いざ必要となったら役立ちそう。

Lightning Talks

JSON schemaによるvalidationの話や、pep8に従うようにコードを変換する autopep8 の話などいろいろ。 トップバッターがいい意味でコメディアンしてて、会場を沸かせていてすごかった。

全体を通して

すべてのトピックがPython、そして国外エンジニアの発表も多数あり、おもしろい話が多く聞けてよかった。 いろいろ聞いてるうちに、__init__以外のSpecial method namesも知っとかないとな、と思ったりもした。 あと、ランチで出てきた弁当がおいしかった。

総じて、Pythonコミュニティの盛り上がりを感じられるよいイベントでした。 スタッフのみなさまありがとうございました & おつかれさまでした。

virtualenvとpipでPyPIパッケージをローカルインストールして使う

PythonPyPIパッケージをローカルインストールして使うには、virtualenvを使う。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 12.04.2 LTS
Release:        12.04
Codename:       precise

まず、apt等のパッケージマネージャでvirtualenvをインストールする。 このとき、Python用のパッケージマネージャpipも一緒に入る。

$ sudo apt-get install python-virtualenv

適当なディレクトリに移動した後、次のようにすることでENV以下に仮想Python環境が作成される。

$ mkdir test
$ cd test/
$ virtualenv --distribute ENV
New python executable in ENV/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.

仮想Python環境を有効にするには次を実行する。 仮想環境に入っている間は、シェルのプロンプトに環境名が表示される。

$ source ENV/bin/activate

仮想環境に入った状態でpipコマンドを実行することで、ライブラリがローカルインストールされる。 たとえば、次の例では高機能対話シェルbpythonをインストールする。

(ENV)$ pip install bpython
Downloading/unpacking bpython
  Downloading bpython-0.12.tar.gz (130Kb): 130Kb downloaded
  Running setup.py egg_info for package bpython

    warning: no files found matching 'bpython/translations/*/LC_MESSAGES/bpython.mo'
Downloading/unpacking pygments (from bpython)
  Downloading Pygments-1.6.tar.gz (1.4Mb): 1.4Mb downloaded
  Running setup.py egg_info for package pygments

Installing collected packages: bpython, pygments
  Running setup.py install for bpython

    warning: no files found matching 'bpython/translations/*/LC_MESSAGES/bpython.mo'
    Installing bpython-urwid script to /home/user/tmp/test/ENV/bin
    Installing bpython script to /home/user/tmp/test/ENV/bin
    Installing bpython-gtk script to /home/user/tmp/test/ENV/bin
  Running setup.py install for pygments

    Installing pygmentize script to /home/user/tmp/test/ENV/bin
Successfully installed bpython pygments
Cleaning up...

(ENV)$ bpython

pip freeze -lは仮想環境内にインストールされているパッケージの一覧を出力する。 この出力をrequirements.txtというファイル名で保存しておけば、これを利用して別の環境で同一のライブラリを揃えることができる。

(ENV)$ pip freeze -l > requirements.txt
(ENV)$ pip install -r requirements.txt

仮想環境から抜けるには、deactiveコマンドを実行すればよい。

(ENV)$ deactivate

PyPIから入れられないパッケージについては、ファイル本体やgit等からインストールすることもできる。 次の例は、現時点でPyPIからのインストールに失敗するdpktをファイル本体からインストールする。

$ pip install https://dpkt.googlecode.com/files/dpkt-1.7.tar.gz

scapyでpcapファイルを逐次的に解析する

Pythonでpcapファイルを解析するにはscapyが便利。scapyを用いてpcapファイルを逐次的に解析するには、下記のようにsniffメソッドを使えばよい。

from scapy.all import *
from datetime import datetime

def analyze(pkt):
    # キャプチャ日時はpkt.timeからunix timestampで得られる
    datetime_str = datetime.fromtimestamp(pkt.time).isoformat()
    return "[%s] %s" % (datetime_str, pkt.summary())

# offline: 読み込むpcapファイルを指定
# filter: BPF形式でフィルタリングルールを指定
# store: 0を指定することで、読み終わったパケットは捨てられ、逐次的に解析できる
# prn: 個々のパケットを引数として関数を呼び出し、その戻り値を標準出力に吐く
sniff(offline="test.pcap", filter="icmp", store=0, prn=analyze)

Pythonでヒープ構造を使う(heapqモジュール)

常に最初の要素が最小値(あるいは最大値)となるようなリストが必要な場合、ヒープ構造を用いることで最小値(最大値)の取り出しをO(1)、要素の追加をO(log n)の時間計算量で行うことができる。

Pythonでヒープを扱う場合、heapqモジュールが使える。

import heapq

a = [6, 3, 2, 4, 5]
heapq.heapify(a)    # 破壊的

print a[0]    # => 2
heapq.heappop(a)    # 最小値をヒープから取り出す
print a[0]    # => 3
heapq.heappush(a, 1)    # ヒープに要素を追加する
print a[0]    # => 1

heapqは最初の要素が最小値となるようなヒープしか扱えない。最初の要素が最大値となるようなヒープ(max-heap)を使う場合は、下記のようにタプルで包んで扱うか、__lt__()の戻り値が本来の結果と逆になるようなクラスを作って扱う必要がある。

a = [6, 3, 2, 4, 5]
a = [(-x, x) for x in a]
heapq.heapify(a)    # 破壊的

print a[0][1]    # => 6
heapq.heappop(a)
print a[0][1]    # => 5
heapq.heappush(a, (-8, 8))
print a[0][1]    # => 8

なんかイマイチ。