Archive for the 'Python' category

Python Decorators 3

Aug 09 2011 Published by under Python

Pythonのデコレータで関数の型チェックなんかをやってみます.デコレータ構文を使うと関数定義のルーチンワーク的な部分を別の関数に切り離し,本質的に重要な部分を強調して記述できます

そもそもデコレータって何?という方は以下の説明をご覧ください

とはいえ,僕にはあんまり高度なことはできませんので,とても簡単な例を通して雰囲気を理解することにとどめます.

Pythonは変数の型は動的に変化する高度な実装になってますので,関数の引数におかしな型の変数が入ってきてもそれを阻止する機構というのはありません.たとえば単純な階乗計算

def factorial(n):
    if n = 0:
        return 1
    else:
        return n * factorial(n-1)

を定義した上で,

>>> factorial("hoge")

とやると,『factorialは文字列型の引数はとれません』とはならず(そりゃそうだけど)に『文字列と整数の引き算はサポートされてない』となります.つまり,引き算やってみたけどダメでしたというエラーになります.また

>>> factorial(-1)

とか

>>> factorial(1.1)

とやると『リカージョンの階層が深すぎですよ』と怒られます.非負整数しか受け入れない関数として正しく定義するためには,

def factorial(n):
    def _factorial(n):
        if n == 0:
            return 1
        else:
            return n * factorial(n-1)

    if isinstance(n, int) and n >= 0:
        return _factorial(n)
    else:
        raise ValueError, "Argument must be a positive integer"

『整数しかダメ』とか『正の数しかダメ』というのはよくある条件なので,関数を定義するたびにこのような条件を入れるのはあまり好ましくありません.コードが読みにくくなってしかたありません.というわけで,この型チェックと範囲チェックをデコレータを使ってやってみましょう

まずは型チェックから

前回・前々回の議論から以下のように書けるかと思います

def type_check(param):
    def _type_check(func):
        def wrapped(arg):
            if isinstance(arg, param):
                return func(arg)
            else:
                raise TypeError, "Argument must be " + param.__name__

        wrapped.__name__ = func.__name__
        wrapped.__doc__  = func.__doc__
        wrapped.__dict__ = func.__dict__
        return wrapped
    return _type_check

@type_check(int)
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

実行すると期待通りの振る舞いをします.つまり,整数以外の引数を入れるとエラーを送出しますが,負の整数を入れるともともと起こっていたリカージョンの深度が大きすぎるというエラーで止まります.負数に対するコントロールはまだできていません.

(型チェックに関するより高度な手法についてはこちらのサイトが参考になります http://d.hatena.ne.jp/griefworker/20110328/python_inspect

次は正数条件をつけます


def range_check(Min = None, Max = None):
    def _range_check(func):
        def wrapped(arg):
            if Min == None and Max == None:
                return func(arg)
            elif Min == None:
                if arg <= Max:
                    return func(arg)
                else:
                    raise ValueError, "Argument must lie in (-Inf,{0}]".format(Max)
            elif Max == None:
                if arg >= Min:
                    return func(arg)
                else:
                    raise ValueError, "Argument must lie in [{0},Inf)".format(Min)
            else:
                if Min <= arg <= Max:
                    return func(arg)
                else:
                    raise ValueError, "Argument must lie in [{0},{1}]".format(Min, Max)
        wrapped.__name__ = func.__name__
        wrapped.__doc__  = func.__doc__
        wrapped.__dict__ = func.__dict__
        return wrapped
    return _range_check

@range_check(Min = 0)
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

Min と Max をデコレータのパラメータとして指定しています.ちょっと長いのは上下限のパラメータが与えられた場合と与えられてない場合に対してそれぞれ処理を書き込んでいるためです.正数条件だけを実装しようとするならもう少し短くできますが,より一般的な形でやってみました.

この場合,正の整数を引数として factorial() に突っ込んだ場合はうまく動きます.負の整数を引数として入れると,望んだ通りのValueError が送出されます.正の小数をを入れると,再帰構文の終了条件 == 0 が満たされることがないので,負の数までさかのぼって factorial() を呼びだそうとします.このタイミングで正数条件が効いてくるので,結果ValueErrorが起こり,正ではない引数が渡されたと怒られます.

最後にくっつける

以上の2つをくっつけると上手く行くはずです.

@range_check(Min = 0)
@type_check(int)
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

デコレータを適用する順番はこの場合はどちらでも問題ありません.両方のチェックを通った引数だけが関数に渡されるので,そうでない場合はエラーメッセージの違いがあるかと思いますが,正しい引数に対してはなんの違いもないはずです.

最後のfactorial()の定義を見て分かるように,使い回しが可能なルーチン(ここでは型チェックと範囲チェック)はデコレータ構文で使えるように一度コーディングしておけば,本質的に重要な部分にフォーカスできます.さらに他の関数を定義する際にももちろん使えるので,コード全体をすっきりさせることができます

デコレータの本格的に実際的な問題に興味のある方は適当な文献を参照してください.たとえばこことか? あるいは,日本語の本ならこれとか:

エキスパートPythonプログラミング
Tarek Ziade
アスキー・メディアワークス
売り上げランキング: 30189

No responses yet

Python Decorators 2

Aug 03 2011 Published by under Computer, Python

おさらい

前回学んだデコレータ式をさらに学んでいきます。素人発想なので間違ってる部分もあるかと思いますが大雑把な感覚はつかめると思います

デコレータ式は典型的には以下のような構文です。

@add_functionality
def base_function():
    # do something

これは,次の同値な構文のシンタックスシュガーです。

def base_function():
    # do something
base_function = add_functionality(base_function)

図で表すとすれば次のようなものになるでしょう。

base_functionadd_functionalityで装飾して新しい機能を追加するということです。add_functionalityの入出力はどちらも関数でなければならないので,add_functionalityの定義内にwrappedを定義し,その内部でbase_functionの装飾を決めていきます。もちろん,「装飾」ということが意味を成すためには,wrappedbase_functionと同じ機能を最低限有していないといけないので(そうでないケースもあるでしょうが),base_functionと同じ引数と戻り値を持つようにします。そういう意味での最小の構成は次のようになります(たぶん。自信はない)

def nodeco(fn):
    # do-nothing decorator
    def wrapped(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapped

このままではデコレートされた関数の名前が変わってしまうので,少し書き加えます.

def nodeco(fn):
    # do-nothing decorator
    def wrapped(*args, **kwargs):
        return fn(*args, **kwargs)
    wrapped.__name__ = fn.__name__ # added
    wrapped.__doc__  = fn.__doc__  # added
    wrapped.__dict__ = fn.__dict__ # added
    return wrapped

__dict__はおまけです.

このコードは元の関数をそのまま返すだけなので意味はないですが,wrappedの中で新たな機能を記述すれば色々なことができそうな気がします.

パラメトライズしてみる

add_functionality関数の振る舞いをパラメータで制御してやることもできます.アイデアは簡単で,add_functionalityを引数を取れるように書き換えてやって,パラメータを代入した後のadd_functionality(param)が,これまでのadd_functionalityと同じように機能すればいいのです(同じ名前を使ってすいません)。つまり,このように書きたいのです

@add_functionality(param)
def base_function(*args,**kw):
    # do something

アットマークの後ろの関数に引数が付いていると思うとややこしいのですが,引数を含めたadd_functionality(param)までが1つの関数で,それを使ってデコレートしていると思えばこの式は容易に理解できると思います.デコレータがパラメータを取れると書かれることも多いようですが,それでは理解しづらいように思います.ここでのadd_functionalityはパラメータを代入したときに初めてデコレータ式を構成できるのだから

イメージ的にはだいたいこんな感じ:

では,なにもしない nodeco をパラメータ対応にしてみましょう.

def nodeco_p(param):
    def inner(fn):
        def wrapped(*args, **kwargs):
            return fn(*args, **kwargs)
        wrapped.__name__ = fn.__name__
        wrapped.__dict__ = fn.__dict__
        wrapped.__doc__  = fn.__doc__
        return wrapped
    return inner

@nodeco_p(0)
def mymult(*args):
    ans = 1
    for x in args:
        ans *= x
    return ans

print(mymult(3,4,7))

ここでは,innerという関数がデコレータの機能を実現しています.nodeco_pは,与えられたパラメータに対して調整された関数innerを返します(この例ではパラメータは読まないですけど...)

もうちょっとまともな例を書いてみます.

def makespan(*params):
    def inner(fn):
        def wrapped(*args,**kw):
            if len(params) == 0:
                spantag = '<span>'
            else:
                value = ''
                for v in params:
                    value += v + ' '
                spantag = "<span class = '" + value[0:-1] + "'>"
            return spantag + fn(*args,**kw) + '</span>'
        wrapped.__name__ = fn.__name__
        wrapped.__dict__ = fn.__dict__
        wrapped.__doc__  = fn.__doc__
        return wrapped
    return inner

@makespan('mail', 'hidden')
def putemailaddress():
    return 'mail at kenjisato.jp'

print putemailaddress()  #=> <span class = 'mail hidden'>mail at kenjisato.jp</span>

putemailaddress()自体はメールアドレスを返すだけの関数ですが,makespanによって装飾されて前後にスパンタグを付加しています.このデコレータはパラメータをとり,パラメータが存在すればスパンタグのクラス属性を指定します.

まとめ

パラメトライズされたデコレータ文について学びました.これは,普通のデコレータの外側にパラメータのコントロールを記述しているだけと思うと簡単に理解できます.

デコレータの続きはまたいつか

No responses yet

Python Decorators 1(デコレータ)

Aug 02 2011 Published by under Computer, Python

Python のデコレータについて勉強してみる。

とりあえず基本的な構文を。デコレータ式とはこんなん:

@add_functionality
def base_function():
    pass

@add_functionality の中にある add_functionality は関数を受けて関数を返す関数です。こいつを上手いこと書いてやるとbase_function に機能を追加できますよということのようです。よくつかう共通の機能をadd_functionality の方にパックしちゃえば,同じことなんべんも書かなくてよくなって記述がシンプルになりますよねってのが根っこにあるアイデアっぽい。

機能を追加するトリックが次の同値な構文で理解できます:

def base_function():
    pass
base_function = add_functionality(base_function)

つまり,上手いこと書かれた add_functionality()base_function を受けて,なんかしらの機能を追加した上で,元の base_function を上書きしてアップデートします。

『Listing-2の何が悪いのか?』といいますと base_function が3回も出てくるのはPythonicじゃない,ということみたいですね。はい。それでListing-1のようなより読みやすく書きやすい代替的な構文(シンタックスシュガー)が使われています。

これで最初のステップはおしまいです。

作ってみる

さて,実際に動くものを書いてみましょう。僕は素人なので実用的な例題は思いつけませんから,よくあるコードから。

関数の実行時間を表示する機能を追加します。名前はなんでもいいですがとりあえず stopwatch にしましょう。

def stopwatch(fn):
    import time
    def wrapped(*args, **kwargs):
        t0 = time.time()
        result = fn(*args, **kwargs)
        t1 = time.time()
        print("{0} : {1}").format(fn.__name__, t1 - t0)
        return result
    return wrapped

Listing-2の精神で

base_function = stopwatch(base_function)

を実行すると,base_functionstopwatch()の戻り値であるwrappedという関数としてアップデートされることが分かります。wrapped関数は Listing 3-1 の3行目,5行目と8行目からbase_functionと同じ引数を取り,同じ戻り値を返す関数であることが分かります。ただし,base_functionの実行の前後に時間の計測を行い(4行目と6行目),さらに実行時間を表示します(7行目)。したがって,引数と戻り値の関係を保ちつつ実行時間の計測という機能を追加できたことになります

動作するコードは例えば次のように作ることができます。

#!/usr/bin/python
# -*- coding: utf-8 -*-

def stopwatch(fn):
    import time
    def wrapped(*args, **kwargs):
        t0 = time.time()
        result = fn(*args, **kwargs)
        t1 = time.time()
        print("{0} : {1}").format(fn.__name__, t1 - t0)
        return result
    return wrapped       

@stopwatch
def wait2sec():
    import time
    time.sleep(2) # 2秒待つ

wait2sec()

wait2secの定義の中に時間計測の機能をつけてももちろんいいのですが,他の関数にも同様の機能を使いたい場合には不便です。

一度だけstopwatchを定義すればよいデコレータ式で機能追加を実現すれば,コードの可読性がはるかに高まります。意味のないコードではありますが,次のようなことが実現できます。

import time

@stopwatch
def wait1sec():
    time.sleep(1) # 1秒待つ

@stopwatch
def wait2sec():
    time.sleep(2) # 2秒待つ

@stopwatch
def wait3sec():
    time.sleep(3) # 3秒待つ

wait1sec()
wait2sec()
wait3sec()

おかしなところ?

試しに,wait1sec.__name__ を見てみましょう。

>>> print(wait1sec.__name__)
wrapped

wait1secの名前が書き換えられてしまっています!まあそりゃそうですね。以下のように書き換えましょう。wrappedの情報を引数の関数の情報で置き換えるだけです。

def stopwatch(fn):
    import time
    def wrapped(*args, **kwargs):
        t0 = time.time()
        result = fn(*args, **kwargs)
        t1 = time.time()
        print("{0} : {1}").format(fn.__name__, t1 - t0)
        return result
    wrapped.__name__ = fn.__name__
    wrapped.__dict__ = fn.__dict__
    wrapped.__doc__  = fn.__doc__
    return wrapped

続きは次回。

No responses yet

John Stachurski EDTC でPythonを学んでいる方のための基本的なお話(2章の補足)

Jun 03 2011 Published by under Python

経済学の院生の中にはStachurski先生のEDTCでPythonをはじめたという人もいるかもしれませんのでそういう人の為のメモを少し.Pythonはインストールされていて,環境変数がきちんと設定されているとします(インストーラでインストールしていれば問題ありません)

Pythonのスクリプトを(Windows上としましょう)実行する方法はいくつかあって,基本はコマンドプロンプトかIPythonを使うのがいいと思います.統合開発環境(IDE)を使って実行ボタンを押すだけというのでもいいですが,まずはコマンドラインの操作に慣れておくのもいいと思います.

コマンドプロンプト

コマンドプロンプトは,Window Vista 以降スタートメニューをクリックしたときにでる『プログラムとファイルの検索』に cmd と打ち込めば候補がでてくると思います.cmd.exe を選択して起動します.こんな感じの画面がでます

キーボードでコマンドを打ち込めば,結果が返されるという状況になります.色々なコマンドが使えるんですが,Pythonの利用だけを目的とするなら, cddir くらいを覚えておけばよいでしょう.

僕の場合,C:DropboxThe Dump (好きな場所にかえてください)にファイルが置いてあるので

カレントフォルダ> cd c:dropboxthe dump
C:DropboxThe Dump> dir

とやると,C:DropboxThe Dump に移動して,そのフォルダの内容を表示します.(Windowsでは大文字・小文字は区別されません)

The Dumpフォルダの中にはhello.py というファイルが入っていました.これをPythonで実行します.こんな感じで入力します.

C:DropboxThe Dump> python hello.py

結果は次のようになります.

ファイル自体は単純なHellow worldです.

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import numpy as np

def main():
    print("Hello, world!")

if __name__ == "__main__":
    main()

EDTCには書かれてないと思いますが,再利用される予定のあるスクリプトに対しては,9,10行目のようなコードを書くことをおすすめします.

if __name__ == “__main__”: 以下に実行時に実行されるコードを書きます.ここに書かれた内容はファイルがモジュールとしてインポートされたときには実行されないので,モジュールのテストをする時などにも役立ちます.

if __name__ == “__main__”: 構文について

Pythonはスクリプトファイル(実行されるファイル)もモジュール(関数を集めたファイル)もさほど区別なく扱うことができます.別のファイルから,import hello とやれば,hello.py で定義されたクラス,関数,変数を利用可能になります.

実行されたスクリプトもインポートされたモジュールもオブジェクトとして扱われますが,__name__ というアトリビュートを使って区別することができます.たとえば,別のPythonスクリプトの中で

import hello

と,helloをインポートすると,hello というオブジェクトができます.同時に__name__ という変数もつくられて,モジュール名が代入されます(だから,’hello’ ですね).インデントされていないブロックは基本的には全部実行されるのですが,__name__ == “__main__” はFALSEになりますので,実行されません.この部分がなく,print(“Hello, world!”) をインデントされていないブロックに書くと,インポートのたびに Hello, world が出力される結果となります.

スクリプトを実行したときにも,変数 __name__ はできますが,この場合中身は,ファイル名ではなく “__main__” が代入されます(予想できましたよね?)

出力結果のファイルへの出力

計算結果を画面に表示するのではなくファイルに出力したい場合,Pythonでは普通ファイルオブジェクトを作って,そこに出力するようにするのですが,コマンドプロンプトの機能を使うともっと簡単にできます.

ためしに,さっきの hello.py を実行する際に

C:DropboxThe Dump> python hello.py > hello.txt

としてみてください.新たに hello.txt というファイルができて中に Hello, world! と書きこまれていると思います.”>” の左側のコマンドを実行することによる出力を”>” の右側に書かれたファイルに書きこむ(上書きする)という操作をしています.

IPython

インタラクティブな環境でコマンドのチェックをしながらファイルを編集するときにはIPythonが便利ですね(これはEDTCにも書いてるので詳細は省略)

コマンドプロンプトと同じように,cd が使えます.でも,dir は別の関数に割り当てられてて使えません.代わりに ls を使います.

スクリプトを実行するときは,python コマンドではなく,run を使います.ファイル名の拡張子を入力する必要はありません.

以上! Enjoy!!

No responses yet

IPython の色

Mar 23 2011 Published by under Python

WindowsでIPython (+ Pyreadline)を使っていて,バックグラウンドが黒いのが嫌な場合は,起動後にWindow上部の枠(なんていうんだろう?)を右クリックして,プロパティで文字色(→黒)・背景色(→白)を変えます

これだけだと In [..] とか Out [..] の部分の色がおかしいので

ショートカットリンクの起動時オプションとして

-colors LightBG

を入れます.

他のオプションはここをみてください

No responses yet

IPython 覚書き ~ 起動時フォルダからジャンプ

Feb 23 2011 Published by under Computer, Python

なんでこんなことに最初から気づかないんだろう...

IPython の起動時フォルダとは別の所で作業をしたいときに,いちいち CD で移動してたんだけど何回もおんなじことやるの馬鹿馬鹿しいので,スクリプトでジャンプできるようにする。それだけ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
os.chdir(r'C:My DropboxDestination')

プロジェクトごとにこのスクリプトを作って起動時フォルダにおく。作業開始と同時にRUNすると幸せになった。

No responses yet