2009-09-11 24 views
18

我夢想一個Python方法有明確的關鍵字參數的個數:獲取的關鍵字參數實際傳遞給一個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 

所以:有這樣的咒語?就我而言,我碰巧能夠比較每個論點與默認值來找出不同的論點,但是當你有九個論點時,這是一種不雅而乏味的事情。對於加分,提供了一個咒語,可以告訴我,即使調用者傳遞關鍵字參數把它的缺省值:

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

調皮!

編輯:(詞法)函數簽名必須保持不變。它是公共API的一部分,顯式關鍵字參數的主要價值在於它們的文檔價值。只是爲了讓事情變得有趣。 :)

+0

這個問題有一個非常相似的標題,但我不完全確定它是否是重複的:http://stackoverflow.com/questions/196960/can-you-list-the-keyword-arguments-a-python功能 - 收到 –

+0

@AndersonGreen。你提到的問題與此無關。它詢問如何在使用**符號傳遞給不接受所有關鍵字的方法時過濾字典。 –

+0

這是一個很好的問題。 –

回答

23

我的靈感來自於失去了理論的裝飾善良,與它玩耍關於後一點想出了這一點:

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 

它打印此:

 
I'm only passing a 
    a: a 
Here's b and c 
    b: True 
    c: c 
All defaults 
    a: None 
    b: False 
    c: 
    d: 0 
Nothin' 
Invalid kwarg 
    func() got an unexpected keyword argument 'e' 

我很高興有了這個。更靈活的方法是將要使用的屬性名稱傳遞給裝飾器,而不是將其硬編碼爲「actual_kwargs」,但這是說明解決方案的最簡單方法。

嗯,Python很好吃。

+0

嘿,很好。我也喜歡這種方法。謝謝! –

5

一種可能性:

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

督察,它很容易檢查傳入的名稱是可接受的集合以及其他方式籌集任何你想要的Python提高(類型錯誤,我猜;-)。很容易變成裝飾,順便說一句。

另一種可能性:

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

通過使一個唯一的對象_sentinel您刪除的風險,即主叫方可能是偶然路過None(或其他非唯一默認值的主叫方可能會通過可能)。這一切都是object()是順利的,順便說一句:一個非常輕量級,獨特的哨兵,不可能意外地與任何其他物體混淆(當你檢查與is運營商)。

兩種解決方案都適用於稍有不同的問題。

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') 
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) 

,其輸出:

 
Inside foo 
Inside foo 
Inside foo 
Inside foo 
Traceback (most recent call last): 
    File "a.py", line 18, in 
    foo(bar=6) 
    File "a.py", line 9, in foo 
    CompareArgs(kwargs, a=None, b=None, c=None) 
    File "a.py", line 5, in CompareArgs 
    raise ValueError("invalid args") 
ValueError: invalid args 

這可能可以轉換爲裝飾者,但米裝飾者需要工作。作爲練習留給讀者:P

0

如果他們通過任何*參數可能會引發錯誤?

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())) 

將它分解成一個varname列表或一個通用解析函數來使用它會很簡單。它不會太硬,使之成爲一個裝飾(蟒蛇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已經包裹的功能或功能已經。

9

這是最簡單,最簡單的方法:

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函數here

+0

+1我從SO那裏學到了很多關於Python的簡潔的東西。 –

+3

不錯 - 關閉 - 但c不應該在那裏。它不會告訴我你通過了哪一個。 –

0

魔術不是答案:

def funky(a=None, b=None, c=None): 
    for name, value in [('a', a), ('b', b), ('c', c)]: 
     print name, value 
+1

...什麼(你想說)? a.k.a.男人,我不明白 – n611x007

+0

不要做任何神奇的事情。這只是混亂。 –

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」運算符,調用者可以傳遞一個空字典作爲參數值,我們仍然會選擇他們不想傳遞的值。我們也通過這種方式避免討厭的裝飾器,並保持我們的代碼更清潔。

+0

由於缺失並不是真正意義上的字典,爲什麼不使用「object()」?這是一個獨特的(這是點)對象的實例。任何事情都不可用(不適用)。 哦,並且del在模塊末尾丟失了,所以它不能被導出,如果你想要安全的話。 –

+1

你不能刪除它;這些函數需要它在全局範圍內,因爲它們從那裏引用它。如果你真的擔心,你可以用下劃線加前綴。對象或字典會起作用;我沒有意識到任何其他的嚴格好處,除了{}比對象()更簡潔。 –

相關問題