デフォルトマネージャについての補足
重要なところの説明が抜けていたので補足。
先にまとめ
get_query_setをオーバーライドしたマネージャを作ると地雷原になりうるので、オーバーライドしないor↑の対策を忘れないようにしましょう。
逆方向リレーションのことを考えると前者がおすすめです。
get_query_set
get_query_setは、そのManagerオブジェクトが最初に生成するQuerySetを返すメソッドです。
デフォルトだと対象モデルのすべてのオブジェクトを返す=なにもフィルタが掛かっていないQuerySetを返します。
これをオーバーライドすると、そのManager全体の挙動を変更できるので
class PublishedMyManager(models.Manager): def get_query_set(self): return super(PublishedMyManager, self).get_query_set().filter(is_deleted=False) class DeletedMyManager(models.Manager): def get_query_set(self): return super(DeletedMyManager, self).get_query_set().filter(is_deleted=True) class MyModel(models.Model): is_deleted = models.BooleanField(u"削除フラグ", default=False) user = models.ForeignKey(User, verbose_name=u"ユーザー") published = PublishedMyManager() unpublished = DeletedMyManager()
下らない例ですがこう定義すると
>>> MyModel.published.all() [is_deleted=Falseのオブジェクトすべて] >>> MyModel.unpublished.all() [is_deleted=Trueのオブジェクトすべて]
という風に
- 意味が分かりやすい
- フィルタを通し忘れて削除されてるデータを表示してしまう危険性などが減る
- フィルタ書くよりコードが短くなる
なコードが掛けていいことづくめ!
大域的な挙動を変えたい時はどんどんManager生やそうぜ!
と思っていました。
1つ目の記事にもどる
4/50
HTTPRequestオブジェクトの手動生成
ビュー関数のテストをするときにてきとうなHttpRequestが欲しいなら
1. django.http.HTTPRequestをつかう
from django.http import HttpRequest request = HttpRequest() request.POST["key"] = "val"
2. django.core.handlers.wsgi.WSGIRequestをつかう
request = WSGIRequest({ "REQUEST_METHOD": "GET", "QUERY_STRING": "key=val&key2=val2", }) #WSGIRequest.GET/POSTはimmutable
POSTリクエストつくるのはめんどくさいか。
3. モック
気合
2/50
デコレータを外す
おはこんばんちわ。情弱王子feizです。
最近とある方の影響で自動テストに凝っておりまして、それはもうセルフプレジャーを覚えた猿のようにテストを書いてるとかいないとかな毎日です。
さて
Pythonでテストをかいてると、たまにデコレータが邪魔になることがあります。
例えばjsonを返すAPIのビューをdjangoでこんなふうにかきました。
class Api(object): @require_GET @json_response @error_handle @validate(MyViewForm) def myview(self, request, params): return { "azuma": params["feiz"], "okano": params["tokibito"], }
- require_GET
- リクエストメソッドをチェック
- json_response
- 辞書をjson.dumpsしてHTTPResponseに加工してヘッダとか設定
- error_handle
- Exceptionをキャッチしてエラーを示す辞書を返す
- validate(Form)
- リクエストパラメータをFormで検証して、cleaned_dataをparamsとしてviewに渡す
とまあjsonを返すのに必要な処理の大半をデコレータに投げてしまえば、viewの方は非常に簡潔に辞書だけ返せばいいよみたいな実装にできるわけです。
問題
このビューの出力をテストするとなると、理想的にはmyviewが返す辞書を検証出来れば万々歳なんですが、当然デコレータがかかっているのでmyviewを呼び出すとHttpResponseが返ります。
これをまたjson.loadsでパースしてうんたらかんたらというのはちょっとおばかすぎる。なんとか任意のタイミングでデコレータをなかった事にしたい。
がんばる
色々調べるとこんな記事が。
デコレータ式を適用した関数から元の関数名を探す - gumi Engineer’s Blog
どうやら関数オブジェクトのfunc_closure(python2.6からは__closure__でも同じものが得られる)というフィールドを覗くとクロージャの中身(?)が見れるらしい。情弱過ぎてさっぱり理解が追いついていない。
とにかくごちゃごちゃと試行錯誤した結果が以下のコード。
import types def strip_decorators(func): naked = func while naked.func_closure is not None: for cell in naked.func_closure: cell = cell.cell_contents if type(cell) is types.FunctionType: naked = cell break return naked def deco1(func): def _deco1(): print "deco1" return func() return _deco1 def deco2(msg): def _deco2(func): def _func(): print msg return func() return _func return _deco2 @deco1 @deco2("jo_jaku") def feiz(): print "feiz" print "call feiz()" feiz() naked_feiz = strip_decorators(feiz) print "\ncall naked_feiz()" naked_feiz()
出力
call feiz() deco1 jo_jaku feiz call naked_feiz() feiz
はずせたっぽいですね。
理解出来ていないところ
とりあえずそれっぽいことができたのはいいんですが、やっぱりきっちり理解しておきたい。
なので今のところ理解できてない部分をまとめておく。
- bold;">func_closureの中のcellってオブジェクトはなんだ: とある方が教えてくれた記事を参考にする。 -> http://d.hatena.ne.jp/atsuoishimoto/20101006/1286338675
- bold;">ほんとにこのコードで完璧に外せるのか: ↑の記事を理解するか、いろんなパターンをためしまくる。
- bold;">bound methodにstrip_decoratorsを適用するとfunctionになって返ってくるのはなぜか: bound methodはどこいった?
つづく
1/50
pythonのimportあれこれ
importでハマって丸一日つぶしたのでメモ。
今回の教材
feiz@dev % tree /home/feiz/tmp/imp/ |-- ddd.py |-- xxx | |-- __init__.py | |-- aaa.py | `-- eee.py `-- yyy |-- __init__.py |-- bbb.py `-- zzz |-- __init__.py `-- ccc.py
各__init__.pyにはprint __name__ + "is loaded"なる文を仕込んでます
init以外のモジュールにはクラスを一個だけ定義しています
ipythonを/home/feiz/tmp/imp/で起動しています
import と from 〜 importの違い
どういう名前でローカル名前空間に束縛するかだけ。
二重にロードされることもない
In [1]: import xxx.aaa xxxis loaded In [2]: from xxx import aaa In [3]: aaa Out[3]: <module 'xxx.aaa' from 'xxx/aaa.py'> In [4]: xxx.aaa Out[4]: <module 'xxx.aaa' from 'xxx/aaa.py'>
モジュールとパッケージ
パッケージをロードしただけではその中にあるパッケージやモジュールは見えません
In [1]: import xxx xxxis loaded In [2]: xxx.aaa --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /home/feiz/tmp/imp/<ipython console> in <module>() AttributeError: 'module' object has no attribute 'aaa'
ただし、いずれかの場所で一度でもロードされていると上位のモジュールからも見えるようになります。
In [3]: from xxx import aaa In [4]: xxx.aaa Out[4]: <module 'xxx.aaa' from 'xxx/aaa.pyc'>
モジュールの中身は普通に見えます。
In [1]: from xxx import aaa xxxis loaded In [2]: aaa.Foo Out[2]: <class 'xxx.aaa.Foo'>
PYTHONPATH
モジュールの名前空間はPYTHONPATHごとに独立しているようです。
なので一つのモジュールに対して2通りのPYTHONPATHを通してしまうと非常にめんどくさいことになります。
feiz@debian14 % export PYTHONPATH=/home/feiz/tmp/imp/yyy/;ipython In [1]: from yyy import zzz yyyis loaded yyy.zzzis loaded In [2]: import zzz zzzis loaded
同じzzzパッケージをインポートしているように見えますが、検索パスが違うので別物としてロードされます。
結果、zzzのinitが2回走ってしまいます。
Djangoとかいうフレームワークではまさにこのようなことが行われており、回避がたいへん難しいバグを引き起こす場合があります。
やめましょう。
おしまい
今回はパッケージのロードだけでは下位モジュールは(普通は)見えないということを知らなかったためにやたらはまりました。
まあいい勉強になったのでよしとします。
hg diff
まことに情けない話ですが、あるブランチ全体のdiffをとるとき*1こんなふうにしてました。
$ hg diff -r 1234:1237 > /tmp/feiz.out
diffを見たいブランチの先頭リビジョンと最終リビジョン番号を調べてその間のdiff。
こんなとり方をすると何が困るかというと、ブランチ作成後にブランチ元から取り込んだ変更分までdiffに入っちゃいます。
こうしましょう。
#branchAはdefaultから切ったとする $ hg up branchA $ hg merge default $ hg ci $ hg diff -r default:branchA
ちゃんとbranchAで加えた変更だけのdiffがとれる。
2行目のブランチ派生元からの取り込みマージは
- diffを正確に出す
- ブランチ元へのマージの時にコンフリクトが起こらないようにする
為に重要なので忘れないようにしましょう。
おしまい
thanks to id:monjudoh
*1:メインブランチへのマージ前にブランチ全体のdiffを見てレビューするなど