はじめに
Pythonでは関数に引数を不定個数渡し、
Pythonにおける引数の渡し方
まずは知識の確認と、
引数をタプルや辞書として参照する
「タプル
In [1]: def foo(a, *args):
...: print args
...:
...:
In [2]: foo(1, 2, 3, 4)
(2, 3, 4)
In [3]: foo(1)
()
同様に
In [4]: def bar(a, **kw):
...: print kw
...:
...:
In [5]: bar(1)
{}
In [6]: bar(1, b=2, c=3, d=4)
{'c': 3, 'b': 2, 'd': 4}
In [7]: bar(b=2, c=3, d=4, a=1)
{'c': 3, 'b': 2, 'd': 4}
タプルや辞書を渡し引数に展開させる
今度は逆にtupleが展開されて仮引数に束縛されるケースです。リスト3の例で、
In [9]: def buzz(a, b, c):
...: print a, b, c
...:
...:
In [10]: xs = (1, 2, 3,)
In [11]: buzz(*xs)
1 2 3
最後は辞書が展開されて仮引数に束縛されるケースです
In [12]: ys = {"c":1, "b":2, "a": 3}
In [13]: buzz(**ys)
3 2 1
組み合わせてみる
これらを組み合わせて使うことができます。
In [14]: foo(*xs)
(2, 3)
この場合はxsの先頭から順番に要素が引数に割り当てられ、
In [15]: bar(**ys)
{'c': 1, 'b': 2}
同様に辞書ysの場合は、
辞書とtupleの両方渡すことも可能ですが、
In [16]: buzz(*xs, **ys)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
TypeError: buzz() got multiple values for keyword argument 'a'
ここまでのおさらいのための簡単なパズル
次のfを、
def f(**kw):
assert kw["class"] == 1
- ※ pythonでclassは予約語です。答えはこの記事の一番最後に。
Pythonでありがちな*args/**kwsの使いかた
標準ライブラリの中にあるものをいくつか見ていきましょう。
オブジェクトのメンバを作るのに使う
このケースでは、
この場合は、
You can create custom local objects by subclassing the local class:
>>> class MyLocal(local):
... number = 2
... initialized = False
... def __init__(self, **kw):
... if self.initialized:
... raise SystemError('__init__ called too many times')
... self.initialized = True
... self.__dict__.update(kw)
... def squared(self):
... return self.number ** 2
今度はXMLParserでの例です。辞書kwのkeyをチェックしてインスタンス変数に代入しています。
class XMLParser:
attributes = {} # default, to be overridden
elements = {} # default, to be overridden
# parsing options, settable using keyword args in __init__
__accept_unquoted_attributes = 0
__accept_missing_endtag_name = 0
__map_case = 0
__accept_utf8 = 0
__translate_attribute_references = 1
# Interface -- initialize and reset this instance
def __init__(self, **kw):
self.__fixed = 0
if 'accept_unquoted_attributes' in kw:
self.__accept_unquoted_attributes = kw['accept_unquoted_attributes']
if 'accept_missing_endtag_name' in kw:
self.__accept_missing_endtag_name = kw['accept_missing_endtag_name']
if 'map_case' in kw:
self.__map_case = kw['map_case']
if 'accept_utf8' in kw:
self.__accept_utf8 = kw['accept_utf8']
if 'translate_attribute_references' in kw:
self.__translate_attribute_references = kw['translate_attribute_references']
self.reset()
このように、
下位にそのまま渡す
前項で説明した
def runfunc(self, func, *args, **kw):
result = None
if not self.donothing:
sys.settrace(self.globaltrace)
try:
result = func(*args, **kw)
finally:
if not self.donothing:
sys.settrace(None)
return result
decoratorを実装する場合も、
def with_warning_restore(func):
@wraps(func)
def decorator(*args, **kw):
with warnings.catch_warnings():
# We need this function to warn every time, so stick an
# unqualifed 'always' at the head of the filter list
warnings.simplefilter("always")
warnings.filterwarnings("error", category=DeprecationWarning)
return func(*args, **kw)
return decorator
処理が不定数個引数をとるケース
substituteが実装されている時点では不定個の引数です。引数に名前をつけてそのkeyを用いてtemplateへの代入を指定します。
>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
heapqのmerge関数です。sort済みの入力を不定複数受け取り、
def merge(*iterables):
'''Merge multiple sorted inputs into a single sorted output.
Similar to sorted(itertools.chain(*iterables)) but returns a generator,
does not pull the data into memory all at once, and assumes that each of
the input streams is already sorted (smallest to largest).
>>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25]))
[0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25]
'''
_heappop, _heapreplace, _StopIteration = heappop, heapreplace, StopIteration
h = []
h_append = h.append
for itnum, it in enumerate(map(iter, iterables)):
try:
next = it.__next__
h_append([next(), itnum, next])
except _StopIteration:
pass
heapify(h)
while 1:
try:
while 1:
v, itnum, next = s = h[0] # raises IndexError when h is empty
yield v
s[0] = next() # raises StopIteration when exhausted
_heapreplace(h, s) # restore heap condition
except _StopIteration:
_heappop(h) # remove empty iterator
except IndexError:
return
新たな機能
PEP 3102で提案された機能"Keyword only argument"
文法を見ても直感的ではないので、
def sortwords(*wordlist, case_sensitive=False):
...
という関数は、
sortwords("Python", "Ruby", "Lisp", "Perl", "Java")
sortwords("Python", "Ruby", "Lisp", "Perl", "Java", case_sensiteive=True)
というような使われかたを想定しています。不定数個の引数としてwordを渡すことができます。
3.
def sortwords(*wordlist, **kwargs):
...
としか書けません。ドキュメントに書いた上でkwargsから"case_
さて、
def compare(a, b, *, key=None):
...
この形式が最終的に投入された書式です。これどういうことかというと、
def compare(a, b, key=None):
...
ただしこの形式だと、
compare(1, 2, 3)
と書くことができてしまい、
def f(*arg):
return compare(*arg)
だったらコードの読み手を当惑させるでしょう。keyword only argumentはこのような状況に
最終的な形式の覚えよくする話をすこししましょう。
def compare(a, b, *ignore, key=None)
...
*ignoreはa, b,より後ろに出てくるkeyと名前のついてないものをすべてtupleとしてうけとります。したがって、
def compare(a, b, *ignore, key=None):
if ignore: # If ignore is not empty
raise TypeError
と書くことで、
*ignoreと書くことはタイプ数が多いですし、
具体的にkeyword only引数が3.
任意のmapping objcetを辞書引数として渡す
記事をここまで読んでいると、
>>> def f(**kw):
... print sorted(kw)
...
>>> ud=UserDict.UserDict()
>>> ud['a'] = 1
>>> ud['b'] = 'string'
>>> f(**ud)
['a', 'b']
cgiで出てくるFieldStoreageもmapping objectでしたね。
>>> import cgi
>>> f = cgi.FieldStorage()
>>> f
FieldStorage(None, None, [])
>>> def foo(**kw):
... print(kw)
...
>>> foo(**f)
{}
この仕組みは一見ドキッとしますが、
+ if (!PyDict_Check(kwdict)) {
+ PyObject *d;
+ d = PyDict_New();
+ if (d == NULL)
+ goto ext_call_fail;
+ if (PyDict_Update(d, kwdict) != 0) {
+ Py_DECREF(d);
それは結局、
int
PyDict_Merge(PyObject *a, PyObject *b, int override)
{
register PyDictObject *mp, *other;
register Py_ssize_t i;
PyDictEntry *entry;
/* We accept for the argument either a concrete dictionary object,
* or an abstract "mapping" object. For the former, we can do
* things quite efficiently. For the latter, we only require that
* PyMapping_Keys() and PyObject_GetItem() be supported.
*/
if (a == NULL || !PyDict_Check(a) || b == NULL) {
PyErr_BadInternalCall();
return -1;
}
mp = (PyDictObject*)a;
if (PyDict_Check(b)) {
いま興味があるのはmapping object
else {
/* Do it the generic, slower way */
PyObject *keys = PyMapping_Keys(b);
PyObject *iter;
PyObject *key, *value;
int status;
if (keys == NULL)
/* Docstring says this is equivalent to E.keys() so
* if E doesn't have a .keys() method we want
* AttributeError to percolate up. Might as well
* do the same for any other error.
*/
return -1;
iter = PyObject_GetIter(keys);
Py_DECREF(keys);
if (iter == NULL)
return -1;
for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
if (!override && PyDict_GetItem(a, key) != NULL) {
Py_DECREF(key);
continue;
}
value = PyObject_GetItem(b, key);
if (value == NULL) {
Py_DECREF(iter);
Py_DECREF(key);
return -1;
}
status = PyDict_SetItem(a, key, value);
Py_DECREF(key);
Py_DECREF(value);
if (status
function call with kwsが速くなった
キーワード引数は文字列比較して遅いのではないかと疑っている方は、
まとめ
Pythonでの生活を快適にしているキーワード引数に関して見てみました。3.
謝辞
この場を借りて記事を書く機会をくださった柴田さん、
参考資料
- Monty Python "Argument clinic"
- PEP-3102 Kewrods Only Arguments
- Allow any mapping after ** in calls
- Speed hack for function calls with named parameters
パズルの答え
f(**{"class": 1})