2009-05-06 31 views
12

Tôi đang viết một trình trang trí cần phải gọi các chức năng khác trước khi gọi hàm mà nó đang trang trí. Chức năng được trang trí có thể có các đối số vị trí, nhưng các chức năng mà người trang trí sẽ gọi chỉ có thể chấp nhận các đối số từ khóa. Có ai có một cách thuận tiện để chuyển đổi các đối số vị trí thành các đối số từ khóa không?Python chuyển đổi args sang kwargs

tôi biết rằng tôi có thể nhận được một danh sách các tên biến của hàm trang trí:

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

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

Nhưng tôi không thể tìm ra cách để nói với những gì được thông qua năm địa vị và những gì là như từ khóa.

trang trí của tôi trông như thế này:

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

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

Có cách nào khác hơn là kwargs chỉ so sánh và co_varnames, thêm vào kwargs bất cứ điều gì không trong đó, và hy vọng cho là tốt nhất?

+1

Tại sao bạn bạn cần phải biết những gì args là vị trí? –

+1

Bởi vì tôi cần phải chuyển đổi chúng thành kwargs để gọi trong chức năng hozer. Hàm này chỉ chấp nhận các kwarg, nhưng nó cần phải biết về tất cả các đối số ban đầu được gọi. Vì vậy, tùy thuộc vào việc mọi người có gọi chức năng được trang trí với các đối số vị trí hoặc được đặt tên, chức năng hozer có thể hoặc không thể nhận được tất cả dữ liệu cần thiết. – jkoelker

Trả lời

4

Lưu ý - co_varnames sẽ bao gồm các biến cục bộ cũng như từ khóa. Điều này có thể không quan trọng, vì zip cắt ngắn chuỗi ngắn hơn, nhưng có thể dẫn đến các thông báo lỗi khó hiểu nếu bạn truyền sai số lượng arg.

Bạn có thể tránh điều này bằng func_code.co_varnames[:func_code.co_argcount], nhưng tốt hơn là sử dụng mô-đun inspect. ví dụ:

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

Bạn cũng có thể muốn để xử lý các trường hợp chức năng định nghĩa **kwargs hoặc *args (ngay cả khi chỉ để nâng cao một ngoại lệ khi sử dụng với các trang trí). Nếu chúng được đặt, kết quả thứ hai và thứ ba từ getargspec sẽ trả về tên biến của chúng, nếu không chúng sẽ là Không.

16

Bất kỳ arg nào đã được chuyển vị trí sẽ được chuyển tới * args. Và bất kỳ arg nào được truyền dưới dạng từ khóa sẽ được chuyển đến ** kwargs. Nếu bạn có args giá trị và tên vị trí sau đó bạn có thể làm:

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

để chuyển đổi tất cả chúng vào args từ khóa.

+1

Thực tế, 'kwargs.update (zip (myfunc.func_code.co_varnames, args))' là đủ. 'dict.update' cũng xử lý các bản lặp 2D. – obskyr

5

Vâng, điều này có thể là quá mức cần thiết. Tôi đã viết nó cho gói dectools (trên PyPi), vì vậy bạn có thể nhận được cập nhật ở đó. Nó trả về từ điển có tính đến các tham số vị trí, từ khóa và đối số mặc định. Có một bộ kiểm tra trong gói (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

Tôi đã thấy điều này được sao chép và dán vào hàng chục dự án nguồn mở. Điều này nên được chuyển đến một chức năng mà mọi người có thể gọi dễ dàng hơn! –

8

Nếu bạn đang sử dụng Python> = 2,7 inspect.getcallargs hiện điều này cho bạn ra khỏi hộp. Bạn chỉ cần chuyển cho nó chức năng được trang trí làm đối số đầu tiên, và sau đó phần còn lại của các đối số chính xác như bạn định gọi nó. Ví dụ:

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

tôi đang lập kế hoạch để làm f('p1', 'p2', 'p3', k2='k2', extra='kx1') (lưu ý k1 đang được thông qua địa vị như p3), vì vậy ...

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

Nếu bạn biết chức năng trang trí sẽ không sử dụng **kwargs , thì khóa đó sẽ không xuất hiện trong dict, và bạn đã hoàn thành (và tôi giả sử không có *args, vì điều đó sẽ phá vỡ yêu cầu rằng mọi thứ đều có tên).Nếu bạn làm**kwargs, như tôi đã có trong ví dụ này, và muốn đưa họ với phần còn lại của các đối số được đặt tên, phải mất thêm một dòng:

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

Đây là cách phù hợp để làm điều đó (nếu bạn có Python 2.7 hoặc mới hơn, mà hầu như tất cả mọi người đều làm). –

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