[python]デコレータでfunctools.wrap()を使う
前々回に書いた記事の続き。
functools.wrap()とは?
これはラッパ関数を定義するときに 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でいいんじゃね?と思って本当それで正しいのか確認しようと思った事が始まり。
ということでテスト
自分はこういった仕様の確認をする場合はお決まりで仕様化テスト*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プログラミングの初めの方にに詳しく書かれているはずなので気になる人は読んでください。
- 作者: Tarek Ziade,稲田直哉,渋川よしき,清水川貴之,森本哲也
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2010/05/28
- メディア: 大型本
- 購入: 33人 クリック: 791回
- この商品を含むブログ (90件) を見る
*1:私のオススメはここPython のデコレータ式 (1) | すぐに忘れる脳みそのためのメモ
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の技術使ってできるんじゃないかなーと思って試しに実装してみたのがきっかけ。
出来たもの
jsdo.it(http://jsdo.it/aroma_black/AtQj)に投下したものの横幅の関係で同じ色が縦一列に並ばないのでZIPを置いておきます。
動作はChrome(ver 10.0.642.2 dev)のMac, Win, Linux版で確認しています。
同じWebkitのSafariでも動くと思ったらclassList()メソッドが実装されていないらしく動きませんでした。
仕組み
ただ単にsetIntervalでクラス追加を行ってそのクラスにアニメーションを行う定義をしているだけです。
終了時にイベントでひろってアニメーションのクラスを外すという事をしています。
動画との違い
見た目では、
- 元動画はフェードイン時の四角の色が少し明るい色になっている
- 四角の中身が固定(ニコ動のは歌詞とアイコンが表示される)
- 四角が消えるまでの時間がランダム(たぶん)
- 元動画は6行7列
ぐらいですかね?
アイコンは適当なものが無かったため、先日公開されたW3CのCSS3のものを使ってみましたw
あけてましたおめでとうございます。
遅くなりましたが新年あけましておめでとうございます。
今年もよろしくお願いします。
「1年の計は元旦にあり」という格言に従うならば、今年もダメっぷり全開ブログになると思いますが生温い目で見てやってください。
ということで今年の目標を去年の状況をふりかえりつつ今更ながらたててみました。
Keep
去年の活動を通して今後も続けたいなーと思った事。
Try
去年の活動を通しての今後も続けたいなーと思った事。
Problem
主に去年を振り返ってまずいなーと思ったこと。
全体を通して
去年は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()メソッドみたいにさくっと出来たらいいなと思った。
そうすれば設定ファイルが全部外だし出来るし。
これはそのうち・・・