読者です 読者をやめる 読者になる 読者になる

Block Rockin’ Codes

back with another one of those block rockin' codes

Pythonのメソッドと引数(pydocだけで書いてみた)

Python
# -*- coding:utf-8 -*-
'''
=================================
Pythonのメソッドと引数
=================================

:Author: Jxck
:Created: 2010/06/04
:Tags: python method argment 
:Ripositly: https://jxck@bitbucket.org/jxck/python-args

:abstract:
pythonは引数は色々な渡し方ができます。
メソッドの設計の時思い出せるようちょっとまとめときます。
doctestの方が分かりやすいと思い、記事自体pydocで書いてみました。

上手く使えばメソッドの設計をすっきりさせたりできます。
引数の設計はインタフェースの設計。
インタフェースの設計は、
ユーザビリティとテスタビリティーに大きく関わる。

ちなみにpythonでおなじみ可変長引数(*args,**kwds)は、
エキスパートPyhtonプログラミングでは
"魔法の引数"と紹介され、使用上の注意が書かれています。
正直自分は**kwsで受け取るより、**dictで渡す方が
最近の好みではあります。
(default_keyword()あたりのはなし)

ソースはここにあります。
https://jxck@bitbucket.org/jxck/python-args
もちろん、異論・指摘大歓迎です。
'''
def argment_list(a, b, c):
    '''
    複数返すとタプルとして返る。
    >>> argment_list(1,2,3)
    (1, 2, 3)
    '''
    return a,b,c

def argment_list2(*args):
    '''
    列挙されて入って来たものを、
    受け取った段階でタプルにまとめられる。
    >>> argment_list2(1,2,3)
    (1, 2, 3)
    '''
    return args

def car(car, *cdr):
    '''
    受け取る段階で、タプルを崩すようなことが出来る。
    >>> tpl=(1,2,3)
    >>> car(*tpl)
    1

    多分これがアンパックにあたる。
    '''
    return car

def cdr(car, *cdr):
    '''
    もちろん後ろもとれる。
    >>> tpl=(1,2,3)
    >>> cdr(*tpl)
    (2, 3)
    '''
    return cdr

def keywords_argment(**kwargs):
    '''
    まとめてdictとして扱える
    >>> keywords_argment(arg1=10,arg2=20)
    {'arg1': 10, 'arg2': 20}
    '''
    return kwargs

def default_keyword(arg1=0, arg2=0, arg3=0):
    '''
    デフォルトの引数を定義できる。
    >>> default_keyword()
    (0, 0, 0)

    ここで、以下の様に一部の値があらかじめ用意されていたとする
    >>> dic={'arg1': 10,'arg2': 20}

    デフォルトを適応するためにわざわざ辞書を崩すのはメンドクサイ。。
    >>> default_keyword(dic['arg1'],dic['arg2'])
    (10, 20, 0)

    こう渡せばいい
    >>> default_keyword(**dic)
    (10, 20, 0)

    ちゃんと無かった部分はデフォルト値が適応される。
    このやり方は、あらかじめクラスの中で共有するデータ群を、
    dictにまとめて持っておきたいけど、
    ここの関数を通して増減するから、
    無い時はデフォルトを適応したいなんてときに便利です。
    そのうち具体例を挙げたいと思います。
    '''
    return arg1,arg2,arg3

def partial_demo(arg1, arg2):
    '''
    >>> from functools import partial

    を使えば、最初に定義した関数の引数を、「一部適応済みの関数」を
    新たに作ることが出来る。
    
    >>> fn = partial(partial_demo, 10)

    これで fn は当初の partial_demoの第一引数を10にした
    引数がarg2のみの新しい関数になる。

    >>> fn(20) #arg2だけ与える
    (10, 20)

    デフォルトキーワードを設定することも出来る。
    >>> fn = partial(partial_demo, arg2=20)
    >>> fn(10)
    (10, 20)

    ただし、第一引数にデフォルトキーワードを設定するなら
    >>> fn = partial(partial_demo, arg1=10)

    実行時は引数を指定して渡さないとエラーになる。
    >>> fn(20)
    Traceback (most recent call last):
    ...
    TypeError: partial_demo() got multiple values for keyword argument 'arg1'

    正しくはこう。
    >>> fn(arg2=20)
    (10, 20)

    多分カリー化になってると思う。
    ただ、あまりやると分かりにくくなるので注意。
    '''

    return arg1, arg2


def func_args(arg, func):
    '''
    もちろん関数も引数に渡せる。

    >>> func_args(10, lambda x:x*2)
    20
    '''
    return func(arg)

def closure(arg1):    
    '''
    ついでにクロージャ。
    >>> fn=closure(10)
    >>> fn(20)
    30

    arg1の値は関数が作られた環境に保持されている。
    10はfnの中のプライベートな値の様に振る舞う。
    関数のテンプレートのように使うことが出来る。
    '''
    return lambda arg2: arg1+arg2

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    '''
    この記事は全てpydocで書かれています。
    doctestは
    % python args.py -v
    とかで実行できます。

    ちなみにnoseで書いたunittestもついています。
    こちらは、easy_install noseしてれば
    % nosetests testArgs.py -v
    とかで実行できます。

    追記
    この記事はテストファーストで作ったので、先にごりごりdoctestを書いていたのですが、
    doctestはスペースも見るので、(1,2,3)は(1, 2, 3)と書かないと行けない。
    すると大変なので、コマンドラインで実行してコピペでやることもありますが、
    ipythonで実行すると
    
    In [1]: 
    
    といった感じでそのままは張れません。
    そこで

    In [2]: %doctest_mode

    と実行すると、

    >>>

    になり幸せになれます。

    方法は@shimizukawa先生が教えてくださいました。
    ありがとうございました!
    '''