デフォルトマネージャについての補足

重要なところの説明が抜けていたので補足。

先にまとめ

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

2010年ふりかえり&2011年目標

どうも大変ご無沙汰です。なさけないかぎりです。

ふりかえり

  1. △それなりにしごとができるようになった気がする
  2. ▼ブログちっともかかなかった
  3. ▼新しい言語とか覚えなかった
  4. ▼私生活が破滅だった

改めてリストアップしてみるとなんとも碌でも無い年ですね。

目標

使える言語を増やそう
Rubyあたりと関数型をなにかひとつ
記事を書こう
なんか今まで50記事しか書いてないらしいので、今年で50ぐらいは書こう。週1ペース
勉強会で話そう
そろそろどっかで発表の一つもしておかないと

サイトがあったような

そんなものはなかった

あの痛々しいヘッダは

著作権に配慮

おわり

0/50

pythonのimportあれこれ

importでハマって丸一日つぶしたのでメモ。

基本的なことがら

Pythonのモジュールインポートのしくみ

6.12 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を見てレビューするなど

チケットの粒度

  1. 開発初期とか機能に取り掛かるときは〜〜機能の開発 とかでかい粒度のチケットだけでいい
  2. 成果共有の速度と手軽さが重要。
  3. チケットの子チケットとか切ってるとブランチも多段になっていくので、defaultマージしづらい=共有遅れる=破滅
  4. ひとそろえ動くようになったら細かいの切っていく
  5. django+tracだったらアプリ=コンポーネントで対応させるのがいいか?