2011年10月23日日曜日

第12回Python mini Hack-a-thon参加報告

Python mini Hack-a-thonに参加してきました。
昼食時にPyConJPの寺田さんや世界の小宮さんの話を聴けるなど、
非常に楽しい一日になりました。

で、重要なHack-a-thonで何をやっていたかというと、
まずPython ORMの調査
これはstormというORMを使ってみるというところで進めていました。
とあるRDBで使いたいのだけれどそのRDBでの利用についての記述が一切ない。
試したかったけど自分のマシンにそのRDBをインストールするのが嫌だったので、
SqlAlchemy、elixir、sqlobjectでサンプルコードを書いてsqliteにデータを入れたり消したりということを試して遊んでました。

昼食後眠くなり、ORMの調査は放棄して、
ProjectEulerの問題解いたり、自分のgae上のwebアプリ直したりしてた。
最後の3時間くらいで成果発表用の何かを作成しようと思って、
akb48のメンバーを検索するモジュールを書いてました。

pyfesでのmoriyoshiさんの発表に感銘を受けたので完全にパクリです。詳細はこちら
moriyoshiさんはperlのacmeの移植だそうなんで、移植のパクリです。お恥ずかしい。

せっかくなんでgithubに上げました。(ドキュメント空です。すんません。後追加します。)
https://github.com/shocking-noise/akb48

実行方法は下記の通り
1.必要なモジュールのインストール(BeautifulSoup、yaml)
2.scraperを実行してyaml生成
3.akb48モジュールをインポートして利用

こだわりポイントは誕生日から今日の年齢を算出するところです。
(ググッてコピペレベルですが)

発表後の指摘として、yamlは邪道だという話があったので、
クラスを生成するコードジェネレータ作るように修正したいです。
(本当のファンなら一字一字コードを打つべきという意見も・・・、別にファンでは無・・・(略))


他の方々の発表は興味深いものが多くで、とても面白かったです。
自分もPyPIにあげられるようなライブラリ作れるようになりたいなぁと思いました。
次回も時間が会えばぜひ参加したいです。

主催者の皆様ありがとうございました。

(追記)
調べたら、moriyoshiさんがpyakb48というモジュールをPyPIにあげていたようです。
中身みたのですが、かなりちゃんとつくられていました(doctestとかもある)。
私のは超劣化コピーですね。勉強して出なおしてきます。

2011年8月24日水曜日

Pythonで演算子オーバーロード

今更ながらPythonの演算子オーバーロード について調べたのでメモ。
クラスのメソッドを作成するだけなので 簡単にできますね。


class OverLoadTest(object):
    def __init__(self, a):
        self.a = a

    # +
    def __add__(self, b):
        self.a = self.a + b
    # -
    def __sub__(self, b):
        self.a = self.a - b
    # /
    def __div__(self, b):
        self.a = self.a / b
    # *
    def __mul__(self, b):
        self.a = self.a * b

    def __repr__(self):
        return str(self.a)

if __name__ == "__main__":
    o = OverLoadTest(5)
    print o
    o + 5
    print o
    o - 5
    print o
    o / 5
    print o


普段はJavaで書いているから演算子オーバーロードが頭にないなー。

使ってるライブラリでも演算子オーバーロード見たことない気がする。
(知らないだけか?)
効果的に使えるようになりたいですね。

2011年8月7日日曜日

FlaskでStreaming

久々にFlaskドキュメントページ見てたら、
Streamingのsnipetが掲載されてました。
(もしかして結構前から?)

Werkzeugには0.5から機能としてはあるみたいなんで、
きっと昔からできたんでしょう。

なかなか使い道ないけどTwitterのようなStreamingAPIとかかっこ良いですよね。

つーことでコード。

from flask import Flask
from flask import Response
import time

app = Flask(__file__)

@app.route("/streaming")
def streaming():
    def generate():
        while True:
            time.sleep(1)
            yield "test\n"
    return Response(generate(),direct_passthrough=True,mimetype='text/plain')

if __name__ == "__main__":
    app.run()


ResponseにStreamingするためのジェネレータを渡すだけ。
direct_passthroughはジェネレータを変更させないための引数らしい、wrapして返すため、後で変更されても影響を受けないようになると思われる(WSGIの仕様っぽい)。
mime/typeはお好きなものを。






それにしても簡単だなー。

2011年7月2日土曜日

Pythonで動的にサブクラスを生成

excelとかDBとかにテストケース用のデータを追加したら
勝手にテストクラスにテストケース(つまりテストメソッド)が追加される、
とかそういうことをやりたい。

で、unittest.TestCaseクラスのサブクラスを
テストデータから動的に生成して実行したいと思ったときに、
どうやれば良いのかちょっと悩んだので、メモ。

動的にサブクラスを生成するのは、
非常に単純でtype関数を使えばできる。
type関数は[クラス名]、[親クラスのタプル(多重継承も可)]、[付与したい属性の辞書]で
動的にクラス生成をできる。


import unittest

def create_testmethod():
    """
    テストメソッドを動的に生成
    """
    def testMethod(self):
        self.assertEquals(1,1)
    return testMethod

def create_testclass():
    """
    TestCaseのサブクラスを動的に生成
    """
    test_class = type('TestA', (unittest.TestCase,), {})
    setattr(test_class,'testMethod',create_testmethod())
    return test_class

def suite():
    """
    テストスイートを生成
    """
    suite = unittest.TestSuite()
    test_class = create_testclass()
    suite.addTest(unittest.makeSuite(test_class))
    return suite

if __name__ == "__main__":
    unittest.TextTestRunner().run(suite())


動的にテストクラス(unittest.TestCaseのサブクラス)を生成して、
動的にテストメソッドを追加することができる。
これを応用すればソースコード書かずにテストクラス、テストメソッドを動的に追加するようなプログラムができる。



Quick Lookup:

2011年5月27日金曜日

DebugToolbarをFlaskにも

Flaskいいよー。Flask。
extensionsもガンガン増えてかなり色々なことができるようになりました。

今日のネタはDebugToolbarです。
ちょっとハマったりしたのでそのメモがわりに。
DebugToolbarはDjangoのextensionでもあるものらしく、
レスポンスタイムとかログとかHttpHeaderとか見れるすぐれもの。

Flaskでも使えるとなれば(Djangoでは使ったこと無いけど)これは試してみるしか

いつものようにeasy_install なり pipなりでflask-debugtoolbarをインストールしたらええやないか。

実行する最小セットは下記の通り。

from flask import Flask
#DebugToolbarをインポート
from flaskext.debugtoolbar import DebugToolbarExtension

app = Flask(__name__)
#パネルを登録
app.config['DEBUG_TB_PANELS'] = (
    'flaskext.debugtoolbar.panels.headers.HeaderDebugPanel',
    'flaskext.debugtoolbar.panels.logger.LoggingPanel',
    'flaskext.debugtoolbar.panels.timer.TimerDebugPanel',
)
#Debug用のシークレットキーを追加、デバッグモードON
app.config['SECRET_KEY'] = 'asd'
app.config['DEBUG'] = True

toolbar = DebugToolbarExtension(app)

@app.route('/')
def test():
    app.logger.info("hello")
    return ""

if __name__ == "__main__":
    app.run()

これでローカルホストからは右にデバッグ用の怪しいボタンが出てくるので、
押すと色々なデバッグ情報が見れるようになる。
自分の環境ではflaskがurl_ruleっていう関数がねーみたいなエラーがでたので、
flaskを最新版(0.7)に換えたらOKでした。

こういうのが増えてくると開発しやすくなりますねー。

2011年5月22日日曜日

挿入ソート

アルゴリズムクイックリファレンス買いました。




購入動機は、
・アルゴリズムは大学生以来(しかも1-2年の時)やってないのでかなり鈍っている
・グラフ系のアルゴリズムとか学生時代にほとんどやってない
・プログラミングコンテストの予選を突破できるようになりたいので正しいアルゴリズムをきちんと学びたい(正しいアルゴリズムを選べるようになる、計算量を意識できるようになる)
です。

つーことで早速挿入ソート
(多少Pythonicに直した…つもり)


def sort(a):
    for i, e in enumerate(a):
        insert(a, i, a[i])


def insert(a, pos, value):
    i = pos - 1
    while i >= 0 and a[i] > value:
        a[i + 1] = a[i]
        i = i - 1
    a[i + 1] = value


if __name__ == '__main__':
    data = [6, 3, 2, 1, 5, 4]
    sort(data)
    print data



オーダー
最良 O(n)
平均 O(n^2)
最悪 O(n^2)

挿入ソート特徴
要素数が少ない、最初からほぼ整列しているデータに対して有効

感想
かなり男らしい実装だが、
ほぼソートされている場合insert関数のwhileが小さくなるため、
結構高速に動作するらしい。

がんばって一冊みっちり勉強したいとこです。(意気込み)

2011年5月3日火曜日

Pythonの命名規則のおさらいじゃい

どうもこんにちわ。

いきなりですけどPythonのみでコーディングしている人は少ないのではないでしょうか。
私もJavaとPythonとjavascriptなどの複数の言語を業務で扱うことがあり、
ある程度慣れては来ていますが、命名規則がごっちゃになったりします。

つーことで今更ながらPythonの超基本命名規則(by PEP8)のおさらい。

名称規約
module名lowercasehamegg.py
class名CapWordsclass HamEgg(object):…
exception名CapWordsclass HamEggException(Exception):…
関数名lowercase_with_underscoresdef ham_egg():…
関数名(既存の文脈※)mixedCasedef hamEgg():…
メソッド名lowercase_with_underscoresdef ham_egg(self):…
インスタンス変数名lowercase_with_underscoresself.ham_egg = None
定数UPPER_CASE_WITH_UNDERSCORESHAM_EGG = "ham_egg"

見返して思ったのはモジュール名にアンダースコアつけてるかも。恥ずかし。

※たまにあるビルトイン系の関数名がmixedCaseになっていると混乱する元になりますね。

2011年3月15日火曜日

Flask-Script

みなさんFlaskしてますか。
Flask相変わらず良いですね。
extensionの数も順調に増えていて良いですね。
mongoだのcouchだのの単語がみえてnosqlもバッチリですね。

ところでdjangoのmanage.py便利ですよね。
Flaskでもrunserverとかshellとかやりたい。
そんな時にはflask extensionsにあるFlask-Scriptですよ。

インストールはいつもの通り
easy_installなりpipなりでflask-scriptとしてください。

Webアプリ側はこんな感じ(applicaiton.py)
#web application
from flask import Flask
from flask import jsonify

app = Flask(__name__)

@app.route('/spam')
def spam():
    return jsonify(res='ok')

if __name__ == "__main__":
    app.run()


んでflask-script側。(manage.py)
#script
from flaskext.script import Manager
from application import app 
manager = Manager(app)

#manage.py にhelloコマンドの追加
@manager.command
def hello():
    print 'hello'

if __name__ == "__main__":
    manager.run()

managerにアプリ登録してやるだけです。

あとは
$python manage.py runserver
でサーバ起動とかできます。
$python manage.py shell
でインタラクティブシェル起動できます。

@manager.commandで独自コマンドも簡単に追加できるので重宝しそうです。

optionパーサも組み込まれているみたいなので、
applicationの起動に色々処理を追加したい場合などは使ってみると良いと思います。

詳細はこちらです。
http://packages.python.org/Flask-Script/

2011年2月13日日曜日

Whooshでの横断検索とか

Whooshの機能についての調査をある程度備忘録的に記載しておく。

まずUNIQUEなフィールドについて
Schema定義の際にフィールドのインスタンスにunique=Trueを渡すことで、
同じ値が入った時に上書きがされるようだ。

Solrなどで使われる複数のフィールドを横断して検索できる
DisMax系のモジュールがWhooshでも存在し、結構簡単に使える。

今回の検証スクリプトは下記
# -*- coding: utf-8 -*-
import sys 
from whoosh.index import create_in
from whoosh.fields import *
from whoosh.scoring import *
from whoosh.query import *
from whoosh.qparser import DisMaxParser

#idフィールドをユニークキーとする
schema = Schema(id=ID(stored=True,unique=True),
                name=NGRAM(stored=True),
                ruby=NGRAM(stored=True),
                address=NGRAM(stored=True),
                telephone=TEXT(stored=True))

ix = create_in("indexdir", schema)
writer = ix.writer()
writer.add_document(id=u"001",
                    name=u"コンビニA",
                    ruby=u"こんびにえー",
                    address=u"東京都",
                    telephone=u"0312345678")
writer.add_document(id=u"002",
                    name=u"コンビニB",
                    ruby=u"こんびにびー",
                    address=u"千葉県",
                    telephone=u"0471234567")
writer.commit()
from whoosh.qparser import QueryParser
searcher = ix.searcher()
q = sys.argv[1].decode('utf-8')
#nameとrubyとaddressとtelephoneフィールドを横断検索
parser = DisMaxParser({"name":0.5,"ruby": 0.5,"address": 0.2,"telephone": 0.1},schema)
query = parser.parse(q)

results = searcher.search(query)
for result in results:
    print result

ちょっとずつWhooshに慣れてきた。
かなりLuceneに近い使われ方を想定している模様。
Luceneを使っている人であれば結構簡単に使えるのでは。

形態素系のAnalyzerがないので、きついところもあるかもしれないが、
結構Pluggableに作られているようだし、
MecabなどのAnalyzerも簡単に作れるかも。
次はそこら辺を調べる予定。

2011年2月12日土曜日

Whoosh Fields

Whooshのフィールドの種類の種類は以下。
やっぱりフィールドの種類はluceneに比べるとすごく少ない。
普通の日本語のフィールドだとngram一択かな。


whoosh.fields.ID
シンプルな単一フィールド。
urlやファイルパス、カテゴリなどで使うと良いとのこと。
luceneやsolr経験者だとユニークキーのことかと思うかもしれないが、
この値が同じだと同じドキュメントになるわけではないので注意。
(同一のidのものを追加しても前のドキュメントは上書かれない)


whoosh.fields.STORED
ストアするがインデックス化はしないフィールド。
検索には引っかからないようにしたいデータを入れる。


whoosh.fields.KEYWORD
スペースやカンマで区切られている文字列をindex化するフィールド。
one two,threefourというデータであればone,two,threefourという3つのデータがインデックス化される。
フレーズ検索(続いた文字列群で検索)には対応していない。


whoosh.fields.TEXT
タームポジション(文字列出現箇所)を記憶しているフィールド。
フレーズ検索に対応。


whoosh.fields.NUMERIC
数値フィールド。
intおよびfloatの値に対応。


whoosh.fields.BOOLEAN
booleanフィールド。
TrueかFalseのみ。


whoosh.fields.DATETIME
日時フィールド。


whoosh.fields.NGRAM and whoosh.fields.NGRAMWORDS
ngramフィールドとngramwordフィールド。
ngramフィールドは文字列をn文字で分割してindex化。
2gramだと[test] -> [te] [es] [st]の3つのデータに分割される。

ngramwordフィールドはキーワードをn単位で分割してindex化。
2gramだと[one two three] -> [one two][two three]の2つのデータに分割される。

Whoosh Quick start

WhooshのQuickStartを試す。

from whoosh.index import create_in
from whoosh.fields import *

#スキーマの作成(フィールドはtitleとpathとcontentの3つ)
#stored=Trueのものはインデックスにオリジナルデータを持たせ、検索時に結果として返す。
#stored=False(デフォルトはFalse)のものはindex化されるので全文検索は可能だがオリジナルデータは検索時にはわからなくなっている。
schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT)

#作成したスキーマのインデックスディレクトリ(indexdir)を作成
ix = create_in("indexdir", schema)
#indexwriterを作成
writer = ix.writer()

#ドキュメントをインデックスに追加
writer.add_document(title=u"First document", path=u"/a",
                    content=u"This is the first document we've added!")
writer.add_document(title=u"Second document", path=u"/b",
                    content=u"The second one is even more interesting!")
#コミットの実行 indexファイルへの書き込み
writer.commit()


from whoosh.qparser import QueryParser
#indexsearcherを作成
searcher = ix.searcher()
#検索クエリの作成(contentにfirstを含むドキュメント)
query = QueryParser("content",schema = ix.schema).parse(u"first")
#検索の実行
results = searcher.search(query)
print results[0]

実行結果
Hit {'path': u'/a', 'title': u'First document'}


とりあえず使い始めは簡単。

Python上での全文検索

Pythonで全文検索したくて少し調べた。

pyluceneとWhooshというのがあるらしい。

pyluceneはJavaで作られた全文検索エンジンのluceneの
pythonバインディング版。
jccというpythonからJavaを呼び出すためのライブラリを利用している。
(jccはpyluceneのために作られた模様)
なので実処理はJavaで行われている。

Whooshは後発でpurepythonで書かれている。
LuceneのAPIに似せたAPIでLuceneを知っている人であれば割ととっつきやすいのかも。


どっちも少し触ってみた感想は、


pylucene
長所
・Luceneを知っていればAPI同じで学習のオーバーヘッドが少ない。
・Luceneからも読める。つまりJavaとインデックスを共有できる。(試してないけど当然Solrからも使えるはず)
短所
・インストール面倒。(特にJCC)
・起動処理が遅い。(JCCのinitVMでJavaVMの起動に時間がかかっている印象)


Whoosh
長所
・インストール簡単・
・使い方も結構分かりやすい。
短所
・日本語の機能的にLuceneより劣る(日本語用のAnalyzerが不足している)
不明
・速度的にLuceneより遅い?(検索/インデックス作成が静的型付け言語で行われるか、スクリプト言語で行われるかの差)


Lucene経験者としてはpyluceneがいい感じだが、
Whooshもかなりいい感じなので期待したいところ。

しばらくはWhooshを評価がてら使ってみることにする。

2011年1月22日土曜日

GAE上のFlaskで画像アップ&表示

みなさん。Flask使ってますか。

Flask最高ですね。
最近はWebアプリ作るときは何やるにしてもFlaskでやってます。
Javaのモジュールが必要なときはJythonつかってFlask動かすくらいです。
(これはまた別の話)

GAEも最高ですね。
ということで、Flask,GAEについて今日は書きたいと思います。
まあGAE上でFlask使う方法は色々なところで書かれているので割愛。

今日はGAE上のFlaskで画像のアップロードと表示について
結構時間かかったので備忘録として書きます。

pythonコードは以下のような感じ。

from flask import Flask
from flask import request
from flask import Response
from google.appengine.ext import db

app = Flask(__name__)

@app.route('/image/upload',methods=['POST'])
def upload_image():
    """画像アップロード"""
    uploaded_image = request.files['image']
    image = Image()
    image.content_type = uploaded_image.content_type
    image.image = uploaded_image.stream.read()
    image.put()
    return 'ok'

@app.route('/image')
def disp():
    """keyで表示"""
    image = Image.get(request.args.get('key',''))
    return Response(response=image.image,content_type=image.content_type)

class Image(db.Model):
    """画像保存用エンティティ"""
    content_type= db.StringProperty()
    image = db.BlobProperty()

まず登録時にはBigtable側に画像のバイナリとコンテントタイプを保存しておく。
で表示側にはResponseに画像バイナリとコンテントタイプを返してやる。


画像アップロードのformはこんな感じ

<form action="/admin/image/upload" enctype="multipart/form-data" method="post">
<input type="file" name="image">
<input type="submit" value="upload">


表示側はこんな感じ

<img src="/image?key={{画像のキー}}" >

2011年1月8日土曜日

The Zen of Python

Pythonのインタラクティブシェルでthisをimportすると
The Zen of Pythonを読むことができる。


>>> import this


英語のリハビリとして辞書片手に翻訳してみる。

-翻訳-
醜いよりも美しいほうがいい
わかりにくいよりも分りやすいほうがいい
複雑よりもシンプルなほうがいい
入り組んでいるよりも複合のほうがいい
ネストしているよりもフラットなほうがいい
密度は低いほうがいい
読みやすさが重要
特別な場合でも規則を破るほど特別ではない
けれども実用性は純粋性をしのぐ
明確に沈黙させているので無い限り、決してエラーを黙って通してはいけない
曖昧さに直面したとき、推測するという誘惑に負けてはいけない
明らかな方法がひとつ、なるべくひとつだけあるべき
たとえオランダ人以外には明らかでなかったとしても
何もしないよりは今やる方がいい
けれども今すぐにやらないほうがいいこともたびたびある
説明が大変な実装であるならば、それは良くないアイディア
説明が簡単な実装であるならば、それがきっと良いアイディア
名前空間こそが良いアイディア、これらをどんどんやっていこう


-原文-
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

FlaskでJSONP

PythonのマイクロフレームワークFlaskでは
JSONを返却するためのメソッドjsonifyが定義されている。


from flask import Flask
from flask import jsonify

app=Flask(__name__)

@app.route('/test.json')
def test_json():
return jsonify(res='test')

if __name__ == '__main__':
app.run()



/test.jsonでリクエストすると
{
res:'test'
}
という結果が帰ってくる

JSONPにしたい場合はjsonifyは使えない。



@app.route('/test.jsonp')
def test_jsonp():
import json
request.args.get('callback') + '(' + json.dumps(dict(res='test')) + ')'



というように自分でリクエストパラメータからcallbackを受け取って
付与して返す必要がある。

ただしこのままでは、mimetypeの指定がないので
厳密にはmimetypeの指定を行わなくてはならない。
script/javascript

現状FlaskのJSONP用のmethod(padded_jsonify)が要望として上がっているが、
jsonifyのように*args,**kwdとすべての引数がjsonオブジェクトになるような機能のため
callbackが入る余地が無いみたいで、提案が今は棄却されている模様。