まとめ
ライブラリのラッパー関数において、呼び出し元からライブラリに渡すパラメータを制御したい場合に、**
を活用すると、渡したいパラメータが増えてきてもいちいち引数を追加して回らなくて済むようにできる。
キーワード引数
def func(k1,k2):
print("%s".format(locals()))
という関数を呼ぶとき、普通の呼び方
>>> func("v1","v2")
{'k2': 'v2', 'k1': 'v1'}
に対して、
>>> func(k1="v1",k2="v2")
{'k2': 'v2', 'k1': 'v1'}
のように key=value
の形で与える引数をキーワード引数と呼ぶ。
関数呼び出し時の順序を入れ替えてもkeyが合うところに値を渡してくれる。
>>> func(k2="v2",k1="v1")
{'k2': 'v2', 'k1': 'v1'}
引数のデフォルト値が指定されていない場合、引数が足りないと例外が発生する。
>>> func(k1="v1")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (1 given)
>>> func(k2="v2")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (1 given)
キーワードが重複した場合とか、引数名で使われていないキーワードを指定した場合はエラーになる。
>>> func(k1="v1-1",k1="v1-2")
File "<stdin>", line 1
SyntaxError: keyword argument repeated
>>> func(k3="v3")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() got an unexpected keyword argument 'k3'
辞書を与える
key=value と指定する代わりに、辞書の手前に **
を付けたものを与えて
>>> func(**{"k1":"v1","k2":"v2"})
{'k2': 'v2', 'k1': 'v1'}
>>> func(**{"k2":"v2"})
{'k2': 'v2', 'k1': 'd1'}
のように呼ぶことができる。
二つの書き方を混ぜることもできる:
>>> func(k1="v1",**{"k2":"v2"})
{'k2': 'v2', 'k1': 'v1'}
辞書を受け取る
関数の定義において最後の引数を **name
の形で書くと、キーワード引数の辞書を受け取ることができる。
def func(k0, **kwargs):
print("{0}".format(locals()))
呼び出しは
>>> func("v0")
{'k0': 'v0', 'kwargs': {}}
>>> func("v0",k1="v1",k2="v2")
{'k0': 'v0', 'kwargs': {'k2': 'v2', 'k1': 'v1'}}
>>> func(k0="v0",k1="v1",k2="v2")
{'k0': 'v0', 'kwargs': {'k2': 'v2', 'k1': 'v1'}}
>>> func(**{"k0":"v0","k1":"v1","k2":"v2"})
{'k0': 'v0', 'kwargs': {'k2': 'v2', 'k1': 'v1'}}
のようにできる。ただしキーワードを付けないと
>>> func("v0","v1","v2")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 1 argument (3 given)
のようなエラーになるのでそこだけ注意すること。
ラッパーでのパラメータの引き回し
例えば、 requests.get()
get(url, params=None, **kwargs)
をラップして body を返す関数
import requests
def get_body(url):
r = requests.get(url)
return r.text
if __name__ == '__main__':
print(get_body('http://qiita.com'))
があったとして、timeout を指定したいとなったときに
import requests
def get_body(url, timeout=None):
r = requests.get(url, timeout=timeout)
return r.text
if __name__ == '__main__':
print(get_body('http://qiita.com', timeout=0.1))
としてもよいが、これだと将来 headers とか proxies も指定したいとなったときに引数を増やして回る必要が出てしまう。一方、
import requests
def get_body(url, **kwargs):
r = requests.get(url, **kwargs)
return r.text
if __name__ == '__main__':
print(get_body('http://qiita.com', timeout=0.1))
のようにしておくとその必要がない(引数の仕様を知るには中で呼んでいるライブラリ関数の仕様を見ないといけなくなるので、何をラップしているかの情報がわかりやすいようにしておく必要はある)。ちなみに例で使った requests.get 自体も requests.request のラッパーになっていて、上と同じ方法でパラメータの引き回しがされている。