2013-09-04 22 views
6

Rất liên quan chặt chẽ đến: How can I programmatically change the argspec of a function in a python decorator?Làm thế nào tôi có thể thay đổi lập trình argspec của một hàm * không * trong một trình trang trí python?

Mô-đun trang trí cung cấp phương tiện để thực hiện chức năng trang trí bảo toàn argspec của chức năng được trang trí.

Nếu tôi xác định một hàm không được sử dụng làm trang trí, có cách nào để sao chép argspec của một hàm khác không? trường hợp sử dụng

Ví dụ:

class Blah(object): 
    def foo(self, *args, **kwargs): 
     """ a docstr """ 
     result = bar(*args, **kwargs) 
     result = result**2 # just so it's clear we're doing something extra here... 
     return result 

def bar(x, y, z=1, q=2): 
    """ a more useful docstr, saying what x,y,z,q do """ 
    return x+y*z+q 

Tôi muốn có foo 's argspec trông giống như bar' s, nhưng nguồn gốc ở lại không thay đổi (ví dụ, inspect.getsource(foo) vẫn sẽ hiển thị các result rác). Mục đích chính của việc này là lấy tài liệu nhân sư và trợ giúp tương tác của ipython để hiển thị các đối số thích hợp.

Khi câu trả lời cho câu hỏi khác cho biết, decorator package hiển thị một cách để làm điều này, nhưng tôi bị lạc trong phần thịt của mã đó. Có vẻ như gói decorator đang biên dịch lại nguồn, hoặc một cái gì đó tương tự. Tôi đã hy vọng một cách tiếp cận đơn giản hơn, ví dụ: một cái gì đó như foo.argspec = bar.argspec, sẽ là có thể.

+0

Vâng đó là một số nguồn lông mã trong hàm trang trí đó. Bạn nói đúng, nó đang xây dựng lại chức năng - tôi đề nghị cố gắng sử dụng lại classmethod FunctionMaker.create và xem cách bạn đi. – Hamish

+0

Suy nghĩ về nó mặc dù, nó có vẻ như nó sẽ có nhiều công việc để làm điều đó, hơn là chỉ cải thiện chữ ký gốc, hoặc thêm một docblock thích hợp. – Hamish

+0

'foo' thực sự là một phương thức, vì vậy đối số đầu tiên sẽ _always_ là đối tượng instantiated, trừ khi bạn muốn' foo' là một 'staticmethod'. Nếu không, sẽ rất khó để kiểm tra sự trợ giúp trên 'Blah.foo' và không thấy' self' là đối số đầu tiên trong chữ ký. Không phải để nói nó không phải là do-thể, chỉ cần rằng bạn có thể cần phải nhớ để tiền tố chữ ký 'bar' với' self'. –

Trả lời

4

Trình trang trí chỉ đơn giản là một chức năng làm điều gì đó với chức năng khác. Vì vậy, về mặt kỹ thuật, bạn có thể đặt mã yêu cầu trực tiếp bên dưới phương thức foo và sau đó, về mặt kỹ thuật, bạn sẽ thay đổi foo mà không cần sử dụng một trang trí, nhưng nó sẽ là một mớ hỗn độn khủng khiếp.

Cách dễ nhất để làm những gì bạn muốn là tạo trang trí có chức năng thứ hai (bar trong trường hợp này) làm đối số để biết chữ ký cần sao chép. Mã lớp học sẽ trông giống như sau:

class Blah(object): 
    @copy_argspec(bar) 
    def foo(self, *args, **kwargs): 
     """ a docstr """ 
     result = bar(*args, **kwargs) 
     result = result**2 # just so it's clear we're doing something extra here... 
     return result 

Bạn sẽ phải có bar được xác định trước thay vì sau giờ học.

.
.
.
. . . thời gian trôi qua. . . .
.
.

OK, may mắn là tôi đã tìm thấy một trang trí cũ mà tôi có thể điều chỉnh.

help(Blah.foo) trông như thế này trước khi trang trí:

Help on method foo in module __main__: 

foo(self, *args, **kwargs) unbound __main__.Blah method 
    a docstr 

và sau khi trang trí nó trông như thế này:

Help on method foo in module __main__: 

foo(self, x, y, z=1, q=2) unbound __main__.Blah method 
    a more useful docstr, saying what x,y,z,q do 

Đây là trang trí tôi đã sử dụng:

import inspect 

class copy_argspec(object): 
    """ 
    copy_argspec is a signature modifying decorator. Specifically, it copies 
    the signature from `source_func` to the wrapper, and the wrapper will call 
    the original function (which should be using *args, **kwds). The argspec, 
    docstring, and default values are copied from src_func, and __module__ and 
    __dict__ from tgt_func. 
    """ 
    def __init__(self, src_func): 
     self.argspec = inspect.getargspec(src_func) 
     self.src_doc = src_func.__doc__ 
     self.src_defaults = src_func.func_defaults 

    def __call__(self, tgt_func): 
     tgt_argspec = inspect.getargspec(tgt_func) 
     need_self = False 
     if tgt_argspec[0][0] == 'self': 
      need_self = True 

     name = tgt_func.__name__ 
     argspec = self.argspec 
     if argspec[0][0] == 'self': 
      need_self = False 
     if need_self: 
      newargspec = (['self'] + argspec[0],) + argspec[1:] 
     else: 
      newargspec = argspec 
     signature = inspect.formatargspec(
       formatvalue=lambda val: "", 
       *newargspec 
       )[1:-1] 
     new_func = (
       'def _wrapper_(%(signature)s):\n' 
       ' return %(tgt_func)s(%(signature)s)' % 
       {'signature':signature, 'tgt_func':'tgt_func'} 
        ) 
     evaldict = {'tgt_func' : tgt_func} 
     exec new_func in evaldict 
     wrapped = evaldict['_wrapper_'] 
     wrapped.__name__ = name 
     wrapped.__doc__ = self.src_doc 
     wrapped.func_defaults = self.src_defaults 
     wrapped.__module__ = tgt_func.__module__ 
     wrapped.__dict__ = tgt_func.__dict__ 
     return wrapped 
+0

Vì vậy, bạn đang đề xuất 'copy_argspec' sẽ rất gần giống với trang trí , nhưng sẽ sử dụng 'bar' làm nguồn của argspec thay vì' foo'? Tôi thích ý tưởng và sẽ thử ... – keflavich

+1

@keflavich: Nếu bạn gặp khó khăn, tôi đã thêm trang trí. :) –

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