[python]デコレータでfunctools.wrap()を使う

前々回に書いた記事の続き。

functools.wrap()とは?

Pythonのマニュアルには

これはラッパ関数を定義するときに partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) を関数デコレータとして呼び出す便宜関数です

と書かれているが正直サッパリ。
上記の説明文の下はサンプルコードが書かれていて、更にその下にはドキュメント文字列が失われると書いてある。

まあ要するにデコレータでラップされる側の関数が何かしら上書きされる、ということなんだけど公式のドキュメントにはその場合の例が書いてないので変更してみよう。
するとコードは以下のようになる。

def my_decorator(f):
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example():
    """Docstring"""
    print 'Called example function'

example()
print example.__name__
print example.__doc__

違いは単に@wrap(func)を取っただけ。
これを実行すると、以下のような表示がされる。

Calling decorated function
Called example function
wrapper
None

結果の3,4行目はmy_decoratorの中wrapperの関数の名前とドキュメンテーション文字列が返ってきている。
だから@wraps()を使ってやるとうまくいきますよーという事。

@wraps()無い場合の動きはどうなってるのか?

まずデコレータの仕組みとして流れをさっきのNGだったコードを使って説明。
コードはこれ。

def my_decorator(f):
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example():
    """Docstring"""
    print 'Called example function'

print example.__name__

流れとしては、

最後のprint文でexampleオブジェクトの解決をしようとする。

しかし@my_decoratorがあるので、まずmy_decoratorが呼び出される。この時のmy_decorator()の引数はexampleの関数オブジェクト。

wrapper()関数の定義が読まれる。

wraper()関数の関数オブジェクトが返される。(def my_decorator()内の最後にあるreturn wrapper)

戻ってきたオブジェクト(wrapper()関数)の関数名(__name__プロパティ)の読み込みをして表示。

というような感じで行くはず。たぶん。

何か問題でも?


@wrap()しなかった時の問題を自分なりに考えてみた結果、

  • help()とかdir()などをがデコレータの定義を返してしまう。
  • doctestがあった時にデコレータのdoctestが呼び出される。

かなーと思った。
doctestは特に大きいかもしれない。
その例を示す。

#-*- coding:utf-8 -*-

from functools import wraps

def my_decorator(f):
    def wrapper(*args, **kwds):
        return f(*args, **kwds)
    return wrapper


@my_decorator
def add(x, y):
    """
    2つの引数x, yを足し算した結果を返す。

        >>> add(1, 2)
        3

    """
    return x + y

こいつの意図としてはadd()関数のテストを行おうとしている。
これはverboseオプションをつけて実行すると

3 items had no tests:
    __main__
    __main__.add
    __main__.my_decorator
0 tests in 3 items.
0 passed and 0 failed.
Test passed.

と表示され、"0 tests in 3items." という表示からテスト自体は1つも行われていない事が分かる。
そこでこいつを@wraps()でラップしてやると、

Trying:
    add(1, 2)
Expecting:
    3
ok
2 items had no tests:
    __main__
    __main__.my_decorator
1 items passed all tests:
   1 tests in __main__.add
1 tests in 3 items.
1 passed and 0 failed.
Test passed.

となってテストが行われている事が分かる。
とまあ、こんなところ?

他こういう点でマズイですよってツッコミあったらくれるとうれしいです!

結論

デコレータはちゃんと@wraps()でつつんでやりましょうというお話でした。
次回、デコレータに変数を渡す場合の書き方!(まだデコレータネタで引っ張る人

Pythonのis演算子と==演算子の違い

この記事を書いた動機

Pythonはなんと言っても英語っぽく書けるのが最大の利点なんだから全部isでいいんじゃね?と思って本当それで正しいのか確認しようと思った事が始まり。

実際の仕様

「初めてのPython第3版」のP200によると

オブジェクトが「同等」であるかの比較には==演算子、オブジェクトが「同一」であるかの比較にはis演算子を使う

らしい。

ということでテスト

自分はこういった仕様の確認をする場合はお決まりで仕様化テスト*1を書いてリポジトリに放り込むなり、Wikiに転記するということをしています。
書いたテストと実行結果が以下。
ちなみに環境はUbuntu10.10, Python2.6.6です。

#-*- coding:utf-8 -*-

import unittest

class EqualAndIsOperatorTest(unittest.TestCase):

    def test_numeric(self):
        self.assert_(1 is 1)
        self.assert_(1 is not 2)
        self.assert_(1 is not 1.0)

        self.assert_(1 == 1)
        self.assert_(1 != 2)
        self.assert_(1 == 1.0)

    def test_string(self):
        self.assert_(u'hoge' is not 'hoge')

        self.assert_(u'hoge' == 'hoge')

    def test_sequence(self):
        list_a = [1,2,3]
        list_b = [1,2,3]

        self.assert_(list_a is not list_b)
        self.assert_(list_a ==list_b)

        list_c = [4,5,6]

        self.assert_(list_a is not list_b)
        self.assert_(list_a != list_c)

        list_a = list_b
        self.assert_(list_a is list_b)
        self.assert_(list_a ==list_b)

if __name__ == '__main__':
    unittest.main()

続けて実行結果。

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

うん、まあエラーがないってだけなんだけど。

結論

「実際の仕様」に書いてあったとおりインスタンス同一の演算がis、値同一の演算が==です。
まあ==って__eq__()をオーバーライドすれば独自の定義できますからねー。

Javaはequals()で同値比較、==でインスンタンス同一のチェックなのでJava知ってる人には逆ですよーというとわかり易いんだろうか。

目標達成率

4/52。
若干遅いペースだ・・・

*1:元々はレガシーコードを改善するための手法。詳細はレガシーコード改善ガイドを参照。

インスタンスメソッド用デコレータを書いてみた

そう言えば関数のデコレータってそこそこ書いたことあるけどインスタンスメソッド用のデコレータって作ったことないなと思い挑戦。
通常の関数のデコレータとは微妙に違うようなのでその辺も踏まえて比較もしてみた(最後はどっちでも使えるようにしたけど。

デコレータはいつもどうやって書いたっけ?となるのでメモの意味も兼ねてここに記載。

インスタンスメソッドと関数の違い

Pythonでのインスタンスメソッドは必ず第1引数に自分自身のオブジェクトを表すselfという変数が入る。
下記のコードを実行するとbar()の呼び出したところでメソッドの引数の個数が合わないというエラーを返す。

class Hoge(object):

    def foo(self):
        pass

    def bar():
        pass

hoge = Hoge()
hoge.foo()
hoge.bar()

エラーメッセージは以下。

Traceback (most recent call last):
  File "hoge.py", line 14, in <module>
    hoge.bar()
TypeError: bar() takes no arguments (1 given)

通常の関数へのデコレータ適用

まずただの関数へのデコレータの書き方。

def deco(func):
    def _deco():
        print "====================="
        result = func()
        print "====================="
        return result
    return _deco

def hoge():
    print "hogehoge"

@deco
def deco_hoge():
    print "hogehoge"

print "call hoge()"
hoge()
print ""
print "call deco_hoge()"
deco_hoge()

デコレータがどういう風に解釈されているかというのは別のサイトの参照してもらうとして*1、deco()関数の中の_deco()関数の引数は何もなし。

実行結果は以下。

call hoge()
hogehoge

call deco_hoge()
=====================
hogehoge
=====================

インスタンスメソッドへのデコレータ適用

適当なクラスを作って先ほどのdecoデコレータを単純に適用してみたクラスを作った。

class Piyo(object):
    def piyo(self):
        print "piyopiyo"
    @deco
    def deco_piyo(self):
        print "piyopiyo"

piyo = Piyo()
print "call piyo.piyo()"
piyo.piyo()
print ""
print "call piyo.deco_piyo()"
piyo.deco_piyo()

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

call piyo.piyo()
piyopiyo

call piyo.deco_piyo()
Traceback (most recent call last):
  File "deco.py", line 38, in <module>
    piyo.deco_piyo()
TypeError: _deco() takes no arguments (1 given)

_deco()関数に何やら1つの引数が与えられているらしい。
これはdeco_piyo()メソッドの引数selfだろうと思われるのでとりあえず動くように_deco()関数の引数にselfを追加。(hogeの動作確認の部分はエラーが出るのでとりあえずコメントアウトしておく。詳細は後述。)

def deco(func):
    def _deco(self):
        print "====================="
        result = func(self)
        print "====================="
        return result
    return _deco

これで改めて実行すると以下のように出力される。

call piyo.piyo()
piyopiyo

call piyo.deco_piyo()
=====================
piyopiyo
=====================

通常の関数でもインスタンスメソッドでも動くようにする

インスタンスメソッドで動くように変えると通常の関数に適用されているdecoデコレータの引数の個数が合わなくなるので手っ取り早くリストによる可変引数を適用。
可変引数にしたdecoデコレータは以下。

def deco(func):
    def _deco(*args):
        print "====================="
        result = func(*args)
        print "====================="
        return result
    return _deco

これで実行するとどちらでも動くようになり、以下の出力がされる。

call hoge()
hogehoge

call deco_hoge()
=====================
hogehoge
=====================

call piyo.piyo()
piyopiyo

call piyo.deco_piyo()
=====================
piyopiyo
=====================

最後に

実際はfunctools.wrapsとか使ってやるのが正しいっぽいので、こちらはまた次回にでもやってみる。
この辺はたしかエキPyことエキスパートPythonプログラミングの初めの方にに詳しく書かれているはずなので気になる人は読んでください。

エキスパートPythonプログラミング

エキスパートPythonプログラミング

Pythonのリスト内包表記でRubyのuniqメソッドと同じ事をする

動機

設定値の重複を削るためににスクリプトを組んだのだけど、Pythonだとループで回して新しい配列に突っ込んでとかやらないといけないっぽいので面倒臭いエレガントじゃない。

一応Pythonで単純に書くと以下のようなコードになると思います。

a = [1,3,4,2,3,5]
b = []
for v in a:
    if v not in b:
        b.append(v)
print b # [1, 3, 4, 2, 5]

RubyはArray.uniqを作った

そんなこんなでRubyだとuniqという重複を取り除くメソッドがあるのでこちらを使って処理しました。
コードは

[1,3,4,2,3,5].uniq # [1, 3, 4, 2, 5]

で、どう見てもこっちの方がエレガントです。

一方Pythonでは

listから重複削除というのはどうも無いらしくlist→set→listへキャストすれば重複削れるよという記事があったものの順序までは維持できないので却下。

一応コード。

a = [1,3,4,2,3,5]
print list(set(a)) # [1, 2, 3, 4, 5]

やっぱりPythonicにリスト内包表記

ループ中に簡単な処理書くだけならリスト内包表記で書けるはずだ!ということで20分ぐらいねばった結果出来た。

a = [1,3,4,2,3,5]
print [y for x,y in enumerate(a) if a[:x+1].count(y) == 1] # [1, 3, 4, 2, 5]

何をしているかというと

for x,y in enumerate(a)

ここでリストのインデックスと要素を取り出し、

a[:x+1]

ここでforで回してる配列のインデックスのところまでの配列を切りだし、

if a[:x+1].count(y) == 1

切りだしたリストに要素の中身の数が1だったら、つまり重複が無ければ、新しいリストに追加
と言うことをしています。

結論

Python信者の自分からからみてもRubyの方が綺麗だと思います。
もしくは自分のリスト内包表記のレベルが足りないのか。
せめて内包表記の中で自分を示す予約語的なものがあればいいんだけどなあ・・・
イメージとしてはこんな感じ。

print [x for x in a if x not in self]

ということで、PyチューやエキPyを参考にもうちょっとエレガントなコードを目指す。
リスト内包表記はワンライナー表記と変わらんので突っ込みすぎると逆にクソコード化する諸刃の剣。
なので程々にゆるりと・・・

忘れてましたが

今年の目標達成まで残り50個

CSS3でアニメーションを作ってみた

動機

今年の頭にニコ動を徘徊してたら下記動画のアニメーション部分が面白い事してるなーと思って眺めてたところ、これのフェードイン、フェードアウトのエフェクト程度ならCSS3の技術使ってできるんじゃないかなーと思って試しに実装してみたのがきっかけ。

D

出来たもの

jsdo.it(http://jsdo.it/aroma_black/AtQj)に投下したものの横幅の関係で同じ色が縦一列に並ばないのでZIPを置いておきます。
動作はChrome(ver 10.0.642.2 dev)のMac, Win, Linux版で確認しています。
同じWebkitSafariでも動くと思ったらclassList()メソッドが実装されていないらしく動きませんでした。

css3-animation-etude.zip 直

仕組み

ただ単にsetIntervalでクラス追加を行ってそのクラスにアニメーションを行う定義をしているだけです。
終了時にイベントでひろってアニメーションのクラスを外すという事をしています。

動画との違い

見た目では、

  • 元動画はフェードイン時の四角の色が少し明るい色になっている
  • 四角の中身が固定(ニコ動のは歌詞とアイコンが表示される)
  • 四角が消えるまでの時間がランダム(たぶん)
  • 元動画は6行7列

ぐらいですかね?
アイコンは適当なものが無かったため、先日公開されたW3CのCSS3のものを使ってみましたw

感想

去年の11月にKOF2010に行ったとき「HTML5 〜 ウェブの未来のためにいま何が必要なのか」のセッションで「HTML5とその関連技術はまだ開発環境が未成熟な為今後は環境が普及のカギになる」とおっしゃってました(どなたがおっしゃったは失念)が、全くその通りだなと痛感しました。
そこら辺と比較するとやっぱりまだFlashの方が上かなという気もします。

今後

現時点では

  • 元動画は四角の中に文字を入れてるので同様な事が出来たらいいな
  • Firefox対応

文字に関してはBPM設定して、配列にデータ突っ込んでおいてそれを順番になめていく制御スクリプトを追加ぐらいの改造でできそうなので気が向いたらやってみる。
FirefoxはCSS3のanimationが未実装らしいのでtransitionでやるしかなさそうな感じなので、まー検討レベルで。

あけてましたおめでとうございます。

遅くなりましたが新年あけましておめでとうございます。
今年もよろしくお願いします。

「1年の計は元旦にあり」という格言に従うならば、今年もダメっぷり全開ブログになると思いますが生温い目で見てやってください。
ということで今年の目標を去年の状況をふりかえりつつ今更ながらたててみました。

Keep

去年の活動を通して今後も続けたいなーと思った事。

  • セッションのスピーカーの担当をする事
  • かんばんTODOアプリの開発
    • リリースは3/31締切目処に・・・

Try

去年の活動を通しての今後も続けたいなーと思った事。

  • GAE(Python)
    • サービス何か1つ作る
      • 次世代Web勉強会で発表したかんばんTODOのサーバーサイド?
  • 開発環境整備についての技術習得
    • CIとかVCSとか
      • とりあえずかんばんTODOで導入?
  • アジャイルの習得
    • ポモドーロ法を使って一人アジャイル
    • タイマースタートで脳が切り替わるぐらい習慣化を目指す

Problem

主に去年を振り返ってまずいなーと思ったこと。

  • 習得が中途半端になっている事項を一通り習得する
    • Java
      • EffectiveJava読んで理解する
    • Scala
      • コップ本読んで理解する
  • アウトプットを増やす
    • 技術系の記事を52個ブログに書く(1年=52週の計算)
      • 勉強会に参加したらレポートを必ず書く
      • ついったーで満足しない

全体を通して

去年は2年前に比べると成長できたかなと思ったけどいろいろな勉強会の参加を通して上には上がいるという事を痛感した年だったなーと思います。
それを踏まえて自分のゴールを明確にする必要があると思ったけどKPT見ると一貫性がないなあという感じが否めない。
ま、正直いろんなもの見てきたからあれもこれもやりたいという状態になっているのでそこらへんは無理に押さえつけたりしないでバックログにそっとしまっておこうと思います。
当面の目標としては去年11月末に公開すると宣言したかんばんTODOは3月末までにやってしまいたいと思います。
その開発を通してポモドーロ法やアジャイル、CI環境の構築方法の習得等も付随して出来そうなのでがんばろうと思います。
その後についてはあまり考えてない・・・ので終わり次第また考えるw

今年の目標記事個数まであと52個。

Flaskでテンプレートディレクトリを指定する

動機

PythonにはFlaskというRubyでいうsinatraライクなマイクロフレームワークがあり、こいつのテンプレートエンジンにはJinja2というPHPでいうSmartyライクなテンプレートエンジンが使われている。
で、Flaskでユニットテストを書いていた時に本番環境とテスト用のディレクトリ分けたいと思った。
というのはディレクトリ構造同じにしておいて期待通りのテンプレートがちゃんとレンダリングされてればいいかぐらいのノリだったため。

Jinja2から直接テンプレートディレクトリを指定する

ググってもやはり日本語情報は引っかからなかったのと公式ドキュメントとりあえず10分ほど探してみても見つからないということで、とりあえずついったーに投げたら反応あるかもという他力本願全開で投げたらid:shimizukawaさんからレスを頂いた(id:shimizukawaさん情報ありがとうございました!)
id:shimizukawaさんから頂いた案としては以下。

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('index.html')
template.render()

ただid:shimizukawaさんもFlask側から設定する方法は分からない、分かったら教えてほしいということだったのでこの情報を基に調べてみることに。

Flask側からテンプレートディレクトリを指定する

調べるやいなや公式ドキュメントのAPIの章に思いっきり書いてあった・・・
使い方はflaskインスタンスのjinja_loader属性にFileSystemLoader('hoge_dir')みたいな感じで割り当ててやればよい。
例は以下。

2010/11/9 修正

id:shimizukawaさんから

app = FileSystemLoader('hoge_dir')

だとappを上書きしていると指摘を受けたので修正しました。

app.jinja_loader = FileSystemLoader('hoge_dir')

ここでのappという変数はflaskインスタンスチュートリアルのコードと同じ)
そしてview関数の方は元々あるFlask.render_template()関数を呼び出す

@app.route("/")
def index():
    from flask import render_template
    return render_template('index.html')

もうちょっと全体が見えるように書いたのが以下。

from flask import Flask
from jinja2 import FileSystemLoader

app = Flask(__name__)
app.jinja_loader = FileSystemLoader('hoge_dir')

@app.route("/")
def index():
    from flask import render_template
    return render_template('index.html')

これで割とすっきりしたかなと思う。
ただ、テスト時にはテンプレートのディレクトリは変えてやらないといけない事にご注意を。

でもまあloaderの存在は知らなかったので教えてもらわなかったら未だにドキュメントと格闘していたかもしれない。

全体のソースコード

コード片じゃ分かりにくと思うのでサンプルコードをユニットテスト付きでZIP公開。
http://d.hatena.ne.jp/aroma_black/files/yourapplication.zip?d=download

不満点

flask.config.form_object()メソッドみたいにさくっと出来たらいいなと思った。
そうすれば設定ファイルが全部外だし出来るし。
これはそのうち・・・