2009-09-11 26 views
18

Tôi đang mơ về một phương pháp Python với args từ khóa rõ ràng:Lấy các đối số từ khóa thực sự truyền cho một phương pháp 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) 

Tôi muốn để có được một cuốn từ điển của chỉ những đối số người gọi thực sự chuyển vào phương pháp, giống như **kwargs, nhưng tôi không muốn người gọi có thể chuyển bất kỳ arg ngẫu nhiên cũ nào, không giống như **kwargs.

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

Vì vậy: có câu thần chú như vậy không? Trong trường hợp của tôi, tôi tình cờ có thể so sánh từng đối số dựa trên giá trị mặc định của nó để tìm ra các đối số khác nhau, nhưng đây là loại không phù hợp và trở nên tẻ nhạt khi bạn có chín đối số. Đối với điểm thưởng, cung cấp một câu thần chú có thể cho tôi biết ngay cả khi người gọi đi trong một cuộc tranh luận từ khóa được gán giá trị mặc định của nó:

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

tinh quái!

Chỉnh sửa: Chữ ký chức năng (từ điển) phải duy trì nguyên vẹn. Đó là một phần của API công khai và giá trị chính của từ khóa rõ ràng args nằm trong giá trị tài liệu của chúng. Chỉ để làm những điều thú vị. :)

+0

Câu hỏi này có tiêu đề rất giống nhau, nhưng tôi không hoàn toàn chắc chắn nếu đó là một bản sao: http://stackoverflow.com/questions/196960/can-you-list-the-keyword-arguments-a-python -function-receive –

+0

@AndersonGreen. Câu hỏi mà bạn đề cập không liên quan gì đến câu hỏi này. Nó yêu cầu làm thế nào để lọc một từ điển khi đi với ** ký hiệu cho một phương thức mà sẽ không chấp nhận tất cả các từ khóa. –

+0

Đây là một câu hỏi hay. –

Trả lời

23

Tôi đã lấy cảm hứng từ sự tốt lành decorator mất-lý thuyết, và sau khi chơi về với nó trong một chút đến với điều này:

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 

nào in này:

 
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' 

Tôi đang hạnh phúc Với cái này. Cách tiếp cận linh hoạt hơn là chuyển tên của thuộc tính bạn muốn sử dụng cho trình trang trí, thay vì mã hóa cứng nó thành 'actual_kwargs', nhưng đây là cách tiếp cận đơn giản nhất minh họa giải pháp.

Mmm, Python ngon.

+0

Heh, khá tuyệt. Tôi cũng thích phương pháp này. Cảm ơn bạn! –

5

Một khả năng:

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

IOW, nó rất dễ dàng để kiểm tra xem các thông qua trong tên nằm trong bộ chấp nhận được và nếu không nâng bất cứ điều gì bạn muốn Python để nâng cao (TypeError, tôi đoán ;-). Khá dễ dàng để biến thành một trang trí, btw.

Một khả năng khác:

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

bằng cách làm cho một đối tượng duy nhất _sentinel bạn loại bỏ rủi ro mà người gọi có thể là vô tình đi qua None (hoặc giá trị mặc định không độc đáo khác người gọi có thể có thể vượt qua). Đây là tất cả object() là tốt cho, btw: một trọng lượng nhẹ, độc đáo sentinel mà không thể có thể vô tình nhầm lẫn với bất kỳ đối tượng khác (khi bạn kiểm tra với các nhà điều hành is).

Hoặc là giải pháp thích hợp hơn cho các vấn đề hơi khác nhau.

2

Cách sử dụng trang trí để xác thực kwargs đến?

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

Cung cấp sản lượng này:

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

Có cách có lẽ tốt hơn để làm điều này, nhưng đây là quan điểm của tôi:

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) 

và sản lượng của nó:

 
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 

này có thể được chuyển đổi thành trang trí, nhưng m y trang trí cần làm việc. Còn lại như một bài tập cho người đọc: P

0

Có thể gây ra lỗi nếu chúng vượt qua bất kỳ * args nào?

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

Sẽ đơn giản để đưa nó vào danh sách các biến số hoặc hàm phân tích chung để sử dụng. Sẽ không quá khó để thực hiện điều này vào một trang trí (python 3.1), quá:

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) 

Lưu ý: Tôi không chắc chắn như thế nào điều này sẽ làm việc xung quanh chức năng đã được bọc hoặc chức năng mà có *args hoặc **kwargs rồi.

9

Dưới đây là cách dễ nhất và đơn giản nhất:

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

func(2, "egg") 

này cung cấp cho các đầu ra: {'a': 2, 'c': None, 'b': 'egg'}. Lý do args phải là bản sao của từ điển locals là từ điển có thể thay đổi, vì vậy nếu bạn tạo bất kỳ biến cục bộ nào trong hàm này, args sẽ chứa tất cả các biến cục bộ và giá trị của chúng.

Tài liệu khác về chức năng locals được tích hợp sẵn here.

+0

+1 Tôi đã học được rất nhiều điều gọn gàng về Python từ SO. –

+3

Đẹp - và gần - nhưng c không nên ở trong đó. Nó không nói cho tôi biết cái nào bạn đã vượt qua. –

0

Magic không phải là câu trả lời:

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

... bạn đang cố nói gì? a.k.a. Người đàn ông, tôi không nhận được nó – n611x007

+0

Đừng làm bất cứ điều gì huyền diệu. Nó chỉ là khó hiểu. –

1

này được dễ dàng nhất thực hiện với một trường hợp duy nhất của một đối tượng sentry:

# 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 

Những điều tốt đẹp về phương pháp này là, kể từ khi chúng tôi sử dụng toán tử "is", người gọi có thể vượt qua một dict trống làm giá trị đối số và chúng tôi vẫn sẽ nhận ra rằng họ không có ý định chuyển nó. Chúng tôi cũng tránh những người trang trí khó chịu theo cách này và giữ mã của chúng tôi sạch hơn một chút.

+0

Vì thiếu không thực sự có nghĩa là một dict, tại sao không sử dụng "object()"? Đó là một ví dụ duy nhất (là điểm) của đối tượng. Không thể sử dụng (w/o trickery) cho bất cứ điều gì. Ồ và bị thiếu ở cuối mô-đun, vì vậy không thể xuất, nếu bạn muốn an toàn. –

+1

Bạn không thể xóa nó; các hàm cần nó nằm trong phạm vi toàn cục khi chúng đề cập đến nó từ đó. Bạn có thể tiền tố nó với dấu gạch dưới nếu bạn thực sự lo lắng. Đối tượng hoặc dict sẽ làm việc; Tôi không nhận thức được bất kỳ lợi ích nghiêm ngặt của một trong hai, khác hơn {} là terse hơn đối tượng(). –

Các vấn đề liên quan