2009-05-06 25 views
12

私は装飾している関数を呼び出す前に他の関数を呼び出す必要があるデコレータを書いています。装飾された関数は位置指定の引数を持つことができますが、装飾子が呼び出す関数はキーワード引数のみを受け入れることができます。誰かが位置引数をキーワード引数に変換する便利な方法を持っていますか?Pythonがkwargsにargsを変換する

>>> def a(one, two=2): 
... pass 

>>> a.func_code.co_varnames 
('one', 'two') 

しかし、私は位置的に渡され、どのようなキーワードとしてあったものを伝える方法を見つけ出すことはできません。

は私が装飾された機能の変数名のリストを取得することができます知っています。

私のデコレータは次のようになります。

class mydec(object): 
    def __init__(self, f, *args, **kwargs): 
     self.f = f 

    def __call__(self, *args, **kwargs): 
     hozer(**kwargs) 
     self.f(*args, **kwargs) 

はちょうど、kwargsからとなco_varnamesを比較し、そこには何もないkwargsからする追加、最善の結果を期待以外の方法はありますか?

+1

は、なぜあなたは引数が何であったかを知る必要があります:あなたはた場合、私はこの例であり、名前付き引数の残りの部分とそれらを含めるようは、**kwargsを持って、それが1本の以上のラインを取ります位置? –

+1

私はhozer関数を呼び出すためにそれらをkwargsに変換する必要があるためです。この関数はkwargsだけを受け入れますが、最初に呼び出されたすべての引数について知る必要があります。だから、人々がデコレートされた関数を位置引数または名前付き引数で呼び出すかどうかによって、hozer関数は必要なすべてのデータを取得することもあれば取得できないこともあります。 – jkoelker

答えて

4

注 - co_varnamesには、ローカル変数とキーワードが含まれます。 zipはより短いシーケンスを切り捨てるので、これはおそらく重要ではありませんが、間違った数のargを渡すと、混乱するエラーメッセージが表示される可能性があります。

これはfunc_code.co_varnames[:func_code.co_argcount]で回避できますが、inspectモジュールを使用する方が良いでしょう。すなわち:

import inspect 
argnames, varargs, kwargs, defaults = inspect.getargspec(func) 

また、(デコレータで使用する場合だけ例外を発生させる場合でも)関数は**kwargsまたは*argsを定義する場合に対処することをお勧めします。これらが設定されている場合、getargspecの2番目と3番目の結果は変数名を返します。そうでない場合はNoneになります。

16

位置的に渡されたすべての引数は、* argsに渡されます。キーワードとして渡されたargは** kwargsに渡されます。あなたは、位置引数の値と名前を持っている場合 、あなたが行うことができます。

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args))) 

は、キーワード引数にそれらすべてを変換します。

+1

実際、 'kwargs.update(zip(myfunc.func_code.co_varnames、args))')で十分です。 'dict.update'も2D iterableを扱います。 – obskyr

5

まあ、これは過剰です。私はdectoolsパッケージ(PyPi上)に書きましたので、あなたはそこで更新を入手することができます。これは、位置、キーワード、およびデフォルト引数を考慮して辞書を返します。パッケージ(test_dict_as_called.py)でのテストスイートがあります:

def _dict_as_called(function, args, kwargs): 
""" return a dict of all the args and kwargs as the keywords they would 
be received in a real function call. It does not call function. 
""" 

names, args_name, kwargs_name, defaults = inspect.getargspec(function) 

# assign basic args 
params = {} 
if args_name: 
    basic_arg_count = len(names) 
    params.update(zip(names[:], args)) # zip stops at shorter sequence 
    params[args_name] = args[basic_arg_count:] 
else: 
    params.update(zip(names, args))  

# assign kwargs given 
if kwargs_name: 
    params[kwargs_name] = {} 
    for kw, value in kwargs.iteritems(): 
     if kw in names: 
      params[kw] = value 
     else: 
      params[kwargs_name][kw] = value 
else: 
    params.update(kwargs) 

# assign defaults 
if defaults: 
    for pos, value in enumerate(defaults): 
     if names[-len(defaults) + pos] not in params: 
      params[names[-len(defaults) + pos]] = value 

# check we did it correctly. Each param and only params are set 
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name]) 
           )-set([None]) 

return params 
+0

これを数十のオープンソースプロジェクトにコピーして貼り付けたのを見てきました。これは、人々がより簡単に呼び出せる機能に移行する必要があります。 –

8

あなたは、Pythonを使用している場合> = 2.7 inspect.getcallargsは、箱から出して、あなたのためにこれを行います。最初の引数として装飾された関数を渡し、それから残りの引数を、それを呼び出す予定の通りに渡します。例:あなたが飾ら関数が**kwargsを使用することはありません知っている場合

>>> def f(p1, p2, k1=None, k2=None, **kwargs): 
...  pass 
>>> from inspect import getcallargs 

は私が

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1') 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}} 

を(k1はP3と位置渡されていることに注意してください)f('p1', 'p2', 'p3', k2='k2', extra='kx1')を行うことを計画し、そう...ですそのキーは辞書に表示されず、完了しています(そして、私は*argsがないと仮定しています。それはすべてが名前を持っているという要件を破ります)。

>>> call_args.update(call_args.pop('kwargs')) 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'} 
+0

これは正しい方法です(Python 2.7以降を使用している場合は、ほとんどの人がそうします)。 –

関連する問題