27

私は明示的なキーワード引数を持つPythonメソッドを夢見ています:

def func(a=None, b=None, c=None):
    for arg, val in magic_arg_dict.items():   # Where do I get the magic?
        print '%s: %s' % (arg, val)

のように、呼び出し元が実際にメソッドに渡した引数のみのディクショナリを取得したい**kwargsのですが、とは異なり、呼び出し元が古いランダムな引数を渡せるようにしたくありません**kwargs

>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5

だから:そのような呪文はありますか?私の場合、たまたま各引数をデフォルトと比較して、異なる引数を見つけることができますが、これは一種のエレガントではなく、9つの引数があると面倒になります。ボーナスポイントについては、発信者がデフォルト値を割り当てられたキーワード引数を渡した場合でも教えてくれる呪文を提供します。

>>> func(a=None)
a: None

トリッキー!

編集:(字句)関数の署名はそのままにしておく必要があります。これはパブリックAPIの一部であり、明示的なキーワードargsの主な価値は、それらのドキュメンタリー値にあります。物事を面白くするためだけに。:)

4

8 に答える 8

31

私は失われた理論のデコレータの良さに触発され、それを少し遊んだ後、これを思いついた:

def actual_kwargs():
    """
    Decorator that provides the wrapped function with an attribute 'actual_kwargs'
    containing just those keyword arguments actually passed in to the function.
    """
    def decorator(function):
        def inner(*args, **kwargs):
            inner.actual_kwargs = kwargs
            return function(*args, **kwargs)
        return inner
    return decorator


if __name__ == "__main__":

    @actual_kwargs()
    def func(msg, a=None, b=False, c='', d=0):
        print msg
        for arg, val in sorted(func.actual_kwargs.iteritems()):
            print '  %s: %s' % (arg, val)

    func("I'm only passing a", a='a')
    func("Here's b and c", b=True, c='c')
    func("All defaults", a=None, b=False, c='', d=0)
    func("Nothin'")
    try:
        func("Invalid kwarg", e="bogon")
    except TypeError, err:
        print 'Invalid kwarg\n  %s' % err

これを印刷します:

私は通過しているだけです
  a:a
これがbとcです
  b:真
  c:c
すべてのデフォルト
  a:なし
  b:誤り
  c:
  d:0
Nothin'
無効なkwarg
  func()が予期しないキーワード引数'e'を取得しました

私はこれに満足しています。より柔軟なアプローチは、使用する属性の名前を「actual_kwargs」にハードコーディングするのではなく、デコレータに渡すことですが、これは解決策を示す最も簡単なアプローチです。

うーん、Pythonはおいしいです。

于 2009-09-11T06:25:43.097 に答える
20

これが最も簡単で簡単な方法です。

def func(a=None, b=None, c=None):
    args = locals().copy()
    print args

func(2, "egg")

これにより、次の出力が得られます{'a': 2, 'c': None, 'b': 'egg'}argsディクショナリのコピーである必要がある理由localsは、ディクショナリが変更可能であるためです。したがって、この関数でローカル変数を作成した場合args、引数だけでなく、すべてのローカル変数とその値が含まれます。

locals組み込み関数の詳細については、こちらをご覧ください。

于 2009-09-11T03:37:44.530 に答える
7

1つの可能性:

def f(**kw):
  acceptable_names = set('a', 'b', 'c')
  if not (set(kw) <= acceptable_names):
    raise WhateverYouWantException(whatever)
  ...proceed...

IOW、渡された名前が許容可能なセット内にあることを確認するのは非常に簡単です。そうでない場合は、Pythonで発生させたいものをすべて発生させます(TypeError、私は推測します;-)。ところで、デコレータに変えるのはとても簡単です。

別の可能性:

_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
   ...proceed with checks `is _sentinel`...

一意のオブジェクト_sentinelを作成することにより、呼び出し元が誤って渡す可能性があるリスクNone(または呼び出し元が渡す可能性のある他の一意でないデフォルト値)を取り除くことができます。これはすべてobject()良いことです、ところで:他のオブジェクトと誤って混同される可能性がない非常に軽量でユニークな歩哨(isオペレーターに確認するとき)。

わずかに異なる問題には、どちらの解決策も適しています。

于 2009-09-11T03:31:08.570 に答える
2

デコレータを使用して、着信するkwargsを検証するのはどうですか?

def validate_kwargs(*keys):
    def entangle(f):
        def inner(*args, **kwargs):
            for key in kwargs:
                if not key in keys:
                    raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
            return f(*args, **kwargs)
        return inner
    return entangle

###

@validate_kwargs('a', 'b', 'c')
def func(**kwargs):
   for arg,val in kwargs.items():
       print arg, "->", val

func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')

この出力を提供します:

b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
  File "kwargs.py", line 20, in <module>
    func(d='not gonna work')
  File "kwargs.py", line 6, in inner
    raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
于 2009-09-11T03:34:33.657 に答える
1

これは、歩哨オブジェクトの単一のインスタンスで最も簡単に実行できます。

# Top of module, does not need to be exposed in __all__
missing = {}

# Function prototype
def myFunc(a = missing, b = missing, c = missing):
    if a is not missing:
        # User specified argument a
    if b is missing:
        # User did not specify argument b

このアプローチの良いところは、「is」演算子を使用しているため、呼び出し元が引数値として空のdictを渡すことができ、それを渡すことを意図していないことを認識できることです。また、この方法で厄介なデコレータを避け、コードを少しクリーンに保ちます。

于 2012-05-08T18:46:38.660 に答える
0

これを行うにはおそらくもっと良い方法がありますが、これが私の見解です:

def CompareArgs(argdict, **kwargs):
    if not set(argdict.keys()) <= set(kwargs.keys()):
        # not <= may seem weird, but comparing sets sometimes gives weird results.
        # set1 <= set2 means that all items in set 1 are present in set 2
        raise ValueError("invalid args")

def foo(**kwargs):
    # we declare foo's "standard" args to be a, b, c
    CompareArgs(kwargs, a=None, b=None, c=None)
    print "Inside foo"


if __name__ == "__main__":
    foo(a=1)
    foo(a=1, b=3)
    foo(a=1, b=3, c=5)
    foo(c=10)
    foo(bar=6)

およびその出力:

fooの内部
fooの内部
fooの内部
fooの内部
トレースバック(最後の最後の呼び出し):
  ファイル"a.py"、18行目
    foo(bar = 6)
  fooのファイル"a.py"、9行目
    CompareArgs(kwargs、a = None、b = None、c = None)
  CompareArgsのファイル"a.py"、5行目
    ValueError( "invalid args")を発生させます
ValueError:無効な引数

これはおそらくデコレータに変換できますが、私のデコレータには作業が必要です。読者への演習として残しました:P

于 2009-09-11T03:36:13.690 に答える
0

* argsを渡すと、おそらくエラーが発生しますか?

def func(*args, **kwargs):
  if args:
    raise TypeError("no positional args allowed")
  arg1 = kwargs.pop("arg1", "default")
  if kwargs:
    raise TypeError("unknown args " + str(kwargs.keys()))

使用するvarnamesまたは汎用解析関数のリストを取得するためにそれを因数分解するのは簡単です。これをデコレータ(python 3.1)にするのもそれほど難しくありません:

def OnlyKwargs(func):
  allowed = func.__code__.co_varnames
  def wrap(*args, **kwargs):
    assert not args
    # or whatever logic you need wrt required args
    assert sorted(allowed) == sorted(kwargs)
    return func(**kwargs)

注:これが、すでにラップされている関数、またはすでにラップされている関数を回避するのにどれだけうまく機能するかはわかりませ*args**kwargs

于 2009-09-11T03:37:09.643 に答える
0

魔法は答えではありません:

def funky(a=None, b=None, c=None):
    for name, value in [('a', a), ('b', b), ('c', c)]:
        print name, value
于 2009-09-11T06:53:28.173 に答える