Python / デコレータと引数

前の投稿からの続きです。 前回、最終的に作成したデコレータとその使い方は次のようになりました。


>> def decorator(a_func):
...     return (lambda : ('KOOL', a_func()))
...
>>> @decorator
... def hoge():
...     return 'hoge'
...
>>> hoge()
('KOOL', 'hoge')

これにはまだ、不満点があります。 別の関数fugaが引数を取る場合はどうしましょう。


>>> @decorator
... def fuga(a_str):
...     return ('fuga', a_str)
...

# fugaは引数を必要としている
>>> fuga()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in <lambda>
TypeError: fuga() missing 1 required positional argument: 'a_str'

# lambdaは引数を受け取れない
>>> fuga('!!!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes 0 positional arguments but 1 was given

decoratorを書き変えましょうか。


>> def decorator(a_func):
...     return (lambda x: ('KOOL', a_func(x)))
...

>>> @decorator
... def fuga(a_str):
...     return ('fuga', a_str)
...

>>> fuga('!!!')
('KOOL', ('fuga', '!!!'))

でもfugaはよくても、hogeはどうでしょう?


>>> @decorator
... def hoge():
...     return 'hoge'
...

# lamdaには引数が必要
>>> hoge()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

# hogeは引数を受け取れない
>>> hoge('!!!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in <lambda>
TypeError: hoge() takes 0 positional arguments but 1 was given

デコレータ自体が受けとる関数には、引数の規約が必要なようです。 現状では、「引数を取るか取らないか」、「取るならその数」によって、 デコレータが適用できる関数が決まります。

*argsと**kwds

抜け道があります。 Pythonの*argsと**kwdsが使えます。

関数に引数が渡された場合、 '*'のついた変数は引数のタプルとして扱うことができます。 また、タプルやリストに'*'を付けて関数に渡すと、展開して個別の引数として渡してくれます。


>>> def hoge(*args):
...     print(args)
...     print(*args)
...
>>> hoge(0,1,2)
(0, 1, 2)
0 1 2

同じく、'**'のついた変数は引数の辞書として扱うことができます。 そして、辞書に'**'を付けて関数に渡すと、 展開して個別のキーワード引数として渡してくれます。


>>> def hoge(**kwds):
...     print(kwds)
...     print(**kwds)
...

# ここでは、printが処理できる 'end' を渡しています
>>> hoge(end='hoge\n')
{'end': 'hoge\n'}
hoge

以上を用いてデコレータを記述します。


>>> def decorator(a_func):
...     return (lambda *args,**kwds: ('KOOL', a_func(*args,**kwds)))
...

# 0引数のhoge
>>> @decorator
... def hoge():
...     return 'hoge'
...
>>> hoge()
('KOOL', 'hoge')

# 1引数のfuga
>>> @decorator
... def fuga(a_A):
...     return a_A
...
>>> fuga('fuga')
('KOOL', 'fuga')

# 2引数のvar
>>> @decorator
... def var(a_A, a_B):
...     return (a_A, a_B)
...
>>> var('var_A', 'var_B')
('KOOL', ('var_A', 'var_B'))

# キーワード引数もOK
>>> var(a_B=1, a_A=0) # 順番を入れ替えてみます
('KOOL', (0, 1))

これで、デコレータに渡す関数の引数に関する制限を突破できます。 ただし、引数の制約を取り払ったことで、想定外の関数が実行時に渡されかねません。 防ぐには、デコレータ内でargsとkwdsを検査してやる必要があります。

実用上は引数の規約を作り、それに適合する関数のみがデコレートできる方が都合がよいかもしれません。 その場合、引数に制約がある最初の書き方のままの方がよいということになります。

──続きます。

目録

  1. Python / 不便が便利
  2. Python / 内省的(introspective)とはどういうことか
  3. Python / 第一級関数と無名関数
  4. Python / デコレータは愉快
  5. Python / デコレータと引数
  6. Python / デコレータのカスタム
  7. Python / IndentデコレータとChoiceデコレータ

0 件のコメント:

コメントを投稿