1. 00:28 23rd 1月 2012

    リアクション: 390

    bgnoriからリブログ

    どうか誤解しないで欲しいんだけど、僕は「全てを疑ってかかれ」なんていうつもりはない。いちいちなにもかも疑っていたら大変だし、なによりそんなのすごくつまらない。大切なのは「何が本当に正しいのか」じゃなくて、僕がいま正しいと思っていることと、君がいま正しいと思っていることが違うということだ。「正しさ」というのは人によって違う、そのことを絶対に忘れちゃいけない。

    (中略)

    世界にあるのは「嘘と本当」の2種類じゃない。世界には沢山の「本当」があるだけなんだ。君がどの「本当」を選ぶのかは、君が自分で決めなくちゃいけない。誰かに選んでもらうことはできない。だから、よーく調べて、よーく考えて選ばなくちゃいけない。そして、一番忘れちゃいけないのは、君の目の前にいるあの人は、君とは違う「本当」を選んでいるかもしれない、ということだ。

    君の「本当」を誰かに押し付けるのはやめた方がいい。だからと言って誰かの「本当」を何も考えずに採用するのもあまりおすすめしない。君が努力すべきなのは、2つの「本当」をひとつにすることじゃなくて、それぞれの「本当」のどこが同じで、どこが違っているかをちゃんと理解することだ。

    (中略)

    もし君が「事実」とか「真実」とか「正しい」とか「本当」なんていう言葉に出くわしたら、まず考えなくちゃいけないのは、「それが誰にとって正しいのか」ということだ。君が見ている世界と、彼や彼女が見ている世界は違う。まず想像力をフル回転させて、彼や彼女の目から見たら、この世界はどんな風に見えるかを想像してみることだ。彼が嘘をついているわけじゃない、彼女が間違っているわけでもない。彼や彼女には世界がそういう風に見えているんだ。

     
  2. 03:06 18th 1月 2012

    リアクション: 2

    デコレータについての諸々

    Pythonにおけるデコレータにはメリットとデメリットがある。それらを解説しつつ、そのデメリットをうまいこと回避するようにしているライブラリVenusianの紹介につなげます。

    デコレータについて

    まずはおさらい

    デコレータとは何か。一言で言えば関数をラップする関数を返す関数です。(以下、関数とメソッドを一括りに関数といいます。)

    例えば、こんなメモ化デコレータ。

    def memorize(func):
        cache = {}
    
        def _func(*args):
            if args not in cache:
                result = func(*args)
                cache[args] = result
            else:
                print("hit cache!: %r" % (args,))
            return cache[args]
        return _func
    
    @memorize
    def sum(*args):
        j = 0
        for i in args:
            j += i
        return j
    
    sum(1, 2, 3, 4)
    sum(1, 2, 3)
    sum(1, 2, 3, 4)
    

    実行するとこうなります。

    hit cache!: (1, 2, 3, 4)
    

    先ほど述べた「関数をラップする関数を返す関数」をマップしてみましょう。

    def memorize(func):  # 関数 (4)
        cache = {}
    
        def _func(*args):  # ラップする関数 (2)
            if args not in cache:
                result = func(*args)  # 関数を (1)
                cache[args] = result
            else:
                print("hit cache!: %r" % (args,))
            return cache[args]
        return _func  # を返す (3)
    

    (1)はラップされた、この場合だとsum(*args)関数ですね。 なお、デコレータ構文が存在しなかった以前のバージョンではこうやってました。

    sum = memorize(sum)
    

    デコレータの性質について

    デコレータはJavaのアノテーションとシンタックスは似ていますが、その関数の読み込み時に評価されます。

    def print1(func):
        def _func():
            func()
    
        print(1)
        return _func
    
    @print1
    def foo():
        pass
    

    このコードの場合、読み込み時に1が出力されます。foo()関数は定義されただけで、呼び出されてないことに気をつけてください。

    複雑なデコレータ

    こんなデコレータ付きの関数を見たこともあるでしょう。

    @printn(10)
    def foo():
        pass
    

    デコレータに括弧がついてますね。これは括弧が付いてないデコレータと何が違うのでしょうか。

    種も仕掛けもないのでコードをのせます。

    def printn(n):
        def _print(func):
            def _func():
                func()
    
            print(n)
            return _func
        return _print
    

    一言で言うなら「関数をラップする関数を返す関数「を返す関数」」です。これでデコレータに渡すパラメータを、関数の定義時に切り替えることができますね。

    要件によって、このようにもデコレータをかけること、ありますよね。

    @foo
    @bar
    def baz():
        pass
    

    @foo('bar')('baz')('qux')
    def quux():
        pass
    

    後者はまだ見たことがありませんが、こんなことになる前に何とかしましょう。:-)

    デコレータのメリットとデメリット

    雰囲気はつかめたでしょうか。デコレータには関数の主たる機能以外の付随的な機能をもたせると、関心事の分離ができコードの見通しが良くなります。login_requiredなどはビューの主たる機能ではないですよね。これがメリット。

    ただ残念なことに、デコレータはその定義の仕方から、オリジナルの関数の参照を隠してしまうという欠点があります。

    sum = memorize(sum)
    

    デコレータ構文を使っても実質的にはこれと変わりません。オリジナルのsum(*args)関数は、もはやどこからもアクセスすることができなくなります。(実際にはクロージャなので中を見てくとかありますが、今回は本質的ではないので省きます。)

    参照できなくなることの何が問題なのでしょうか。あなたがテストコードを書かないのであれば、問題になることはまずないでしょう。ただそれはまた別の問題を近い将来に引き起こします。

    テストコードを書く人は、こういう問題にぶち当たったことがあるはずです。

    @christmas_only  # クリスマスの日だけ有効な
    @login_required  # ログインしているユーザだけ有効な
    @dict_to_json  # 辞書をJSON形式に変換したレスポンスを返す
    def merry_christmas(request):
        data = get_tree_data()  # クリスマスツリーデータを辞書で返す
        return data
    

    このビューは「本質的にはクリスマスツリーのデータを返すビュー」です。しかし、問題点がいくつかありますね。

    • クリスマスの日だけ有効だとすると他の日ではどうやってテストするのするのでしょうか。
    • ログインしているユーザのデータを毎回作る?
    • 辞書データをJSON形式へ変換しているので、テストコードで内容のアサーションをしようとするとjson.loads()で先に復元しなければなりません。

    これらの問題点はその関数の主たる機能ではないが、付随しているがためにテストコードの設計に影響を与え続けます。これがデメリット。

    もちろんそれらを回避する方法はあります。

    def _merry_christmas(request):  # こっちの関数をテストする
        data = get_tree_data()  # クリスマスツリーデータを辞書で返す
        return data
    
    @christmas_only  # クリスマスの日だけ有効な
    @login_required  # ログインしているユーザだけ有効な
    @dict_to_json  # 辞書をJSON形式に変換したレスポンスを返す
    def merry_christmas(request):
        return _merry_christmas(request)
    

    DjangoのClassBasedViewもデコレータの観点からは同じ回避方法です。

    自作デコレータであれば、デコレータのアトリビュートにオリジナルの関数を紐付けるという方法もあります。

    def dict_to_json(func):
        def _wrap(*args, **kwargs):
            d = func(*args, **kwargs)
            return json.dumps(s)
        dict_to_json.original_func = func  # これならテストできる
        return _wrap
    

    だけど、ちょっと待ってください。そもそもデコレータがオリジナルの関数を置き換えなければ、こんな面倒なことはしないでよかったのです。そうでしょう?

    Venusianについて

    Venusianは主にフレームワーク作成者のためのライブラリですが、デコレータの使い方に特長があります。

    誤解を恐れずに言うならば「デコレータをアノテーションのように使います」。

    # jsonized_view.py
    import json
    import venusian
    
    def jsonify(wrapped):
        def callback(scanner, name, ob):
            def jsonified(request):
                result = wrapped(request)
                return json.dumps(result)
            scanner.registry[name] = jsonified
        venusian.attach(wrapped, callback)
        return wrapped
    
    @jsonify
    def object_view(request):
        return request.as_dict()
    

    Venusianは2段階に分けデコレータを解釈します。アタッチとスキャン。

    アタッチ

    上のコードのうちアタッチのフェーズで何をやっているのかみてみましょう。

    def jsonify(wrapped):
        def callback(scanner, name, ob):
            ...
        venusian.attach(wrapped, callback)  # コールバックをアタッチして
        return wrapped  # オリジナルの関数を返す
    

    奇妙なことにデコレータに渡ってきたオリジナルの関数をそのまま返してます。 作用だけみると、ラップされていない関数がモジュール内に戻されて、配置されるのがわかります。

    でもこれでうまくいくのでしょうか。

    スキャン

    Venusianはスキャナによるスキャンをおこなうことで、アタッチされた関数を走査します。

    import venusian
    import jsonized_view
    
    registry = {}
    scanner = venusian.Scanner(registry=registry)
    scanner.scan(jsonized_view)
    

    アタッチされた関数には、Venusian用のコールバックcallback(scanner, name, ob)が設定されているので、それをスキャナが呼びます。 あのコードだとregistryにデコレータでラップした関数を登録していますね。 スキャン後のregistryはこのようになるでしょうか。

    {'object_view': <function jsonified at 0x1004ef230>}
    

    結局どうなったの

    整理してみましょう。

    • 関数の定義箇所では、オリジナルの関数がそのまま配置されている。
    • スキャナでスキャンした結果、関数とそのデコレータしたあとの関数の対が得られる。

    このような状況になりました。

    テストのしやすさは改善したでしょうか。モジュール内にはオリジナルの関数がそのまま配置されているので、テストではデコレータの作用を気にせずにコードを書けそうです。

    実はVenusianでおこなう事はこれが全てです。 あとはスキャンした結果を利用するフレームワークが、自身でURLマッピングにそのデコレートされた関数を使用したりします。有名なものではPyramidがVenusianを採用していますね。

    上で述べたように、このフレームワークの仕組みは、デコレータの中身はデコレートされた関数を返すことを要求しないので、関数のカテゴライズや、アノテーション付与のみの目的での利用など、幅広い利用パターンが考えられます。

     
  3. 22:51 17th 1月 2012

    リアクション: 713

    otsuneからリブログ

    会社をやめるので仕事の引継ぎをしているが、自分の後任の人が引き継がれた仕事をやりたくないという理由で自分より早く退職するという、訳のわからない状況になっている。
     
  4. 14:42 10th 1月 2012

    リアクション: 99

    otsuneからリブログ

    【コピペして使ってね】
    さっきマックで女子高生が「彼氏が( )だったんだけど」「マジで別れた方がいいよ」「そうだよね・・・私も( )なんかやめて( )にしようよって何度も言ってるんだけど」「そういう問題じゃないよ( )じゃない男とかあり得ない」とか言ってた
     
  5. sqlalchemy-migrate

    チュートリアルみれば一発だけど、備忘録にメモしておく。

    準備

    インストール

    % pip install sqlalchemy-migrate
    

    migrateコマンドができる。名前があれなのでvirtualenvがあったほうがいいと思う。

    リポジトリ作成

    % migrate create リポジトリパス リポジトリ名
    

    my_repositoryってパスで作成するとこんな構成になる。

    % find my_repository
    my_repository
    my_repository/__init__.py
    my_repository/manage.py
    my_repository/migrate.cfg
    my_repository/README
    my_repository/versions
    my_repository/versions/__init__.py
    

    tip

    酒徳さんの記事にあるようにmanage.pyに設定を直接書き込んじゃうのは、結構よくあるようなので設定しておくと楽。

    % cat my_repository/manage.py
    #!/usr/bin/env python
    from migrate.versioning.shell import main
    
    if __name__ == '__main__':
        main(debug='True',
             repository='my_repository',
             url='sqlite:///project.db')
    

    マイグレーション用にセットアップ

    % python my_repository/manage.py version_control
    

    通常作業

    マイグレーションスクリプトの作成

    先の記事に書かれてたワークフローより簡略化されてて、

    % python my_repository/manage.py script 説明
    

    でナンバリングされたそのスクリプトがリポジトリパス/versions配下に置かれるので、そのまま編集する。

    バージョン確認

    リポジトリのバージョン確認
    % python my_repository/manage.py version
    適用されてるデータベースのバージョン確認
    % python my_repository/manage.py db_version
    

    アップグレード/ダウングレード

    アップグレード
    % python my_repository/manage.py upgrade [バージョン]
    ダウングレード
    % python my_repository/manage.py downgrade [バージョン]