Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
287 views
in Technique[技术] by (71.8m points)

python decorator to display passed AND default kwargs

I am new to python and decorators and am stumped in writing a decorator which reports not only passed args and kwargs but ALSO the unchanged default kwargs.

This is what I have so far.

def document_call(fn):
    def wrapper(*args, **kwargs):
        print 'function %s called with positional args %s and keyword args %s' % (fn.__name__, args, kwargs)
        return fn(*args, **kwargs)
    return wrapper

@document_call
def square(n, trial=True, output=False):
    # kwargs are a bit of nonsense to test function
    if not output:
        print 'no output'
    if trial:
        print n*n

square(6) # with this call syntax, the default kwargs are not reported
# function square called with positional args (6,) and keyword args {}
# no output
36

square(7,output=True) # only if a kwarg is changed from default is it reported
# function square called with positional args (7,) and keyword args {'output': True}
49

The 'problem' is that this decorator reports the args that are passed in the call to square but does not report the default kwargs defined in the square definition. The only way kwargs are reported is if they're changed from their default i.e. passed to the square call.

Any recommendations for how I get the kwargs in the square definition reported too?

Edit after following up on the inspect suggestions, which helped me to the solution below. I changed the output of positional params to include their names because I thought it made the output easier to understand.

import inspect
def document_call(fn):
    def wrapper(*args, **kwargs):            
            argspec = inspect.getargspec(fn)
            n_postnl_args = len(argspec.args) - len(argspec.defaults)
        # get kwargs passed positionally
        passed = {k:v for k,v in zip(argspec.args[n_postnl_args:], args[n_postnl_args:])}
        # update with kwargs
        passed.update({k:v for k,v in kwargs.iteritems()})            
        print 'function %s called with 
  positional args %s
  passed kwargs %s
  default kwargs %s' % (
                fn.__name__, {k:v for k,v in zip(argspec.args, args[:n_postnl_args])},
                passed,
                {k:v for k,v in zip(argspec.args[n_postnl_args:], argspec.defaults) if k not in passed})        
        return fn(*args, **kwargs)
return wrapper

That was a good learning experience. It's neat to see three different solutions to the same problem. Thanks to the Answerers!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You'll have to introspect the function that you wrapped, to read the defaults. You can do this with the inspect.getargspec() function.

The function returns a tuple with, among others, a sequence of all argument names, and a sequence of default values. The last of the argument names pair up with the defaults to form name-default pairs; you can use this to create a dictionary and extract unused defaults from there:

import inspect

argspec = inspect.getargspec(fn)
positional_count = len(argspec.args) - len(argspec.defaults)
defaults = dict(zip(argspec.args[positional_count:], argspec.defaults))

You'll need to take into account that positional arguments can specify default arguments too, so the dance to figure out keyword arguments is a little more involved but looks like this:

def document_call(fn):
    argspec = inspect.getargspec(fn)
    positional_count = len(argspec.args) - len(argspec.defaults)
    defaults = dict(zip(argspec.args[positional_count:], argspec.defaults))
    def wrapper(*args, **kwargs):
        used_kwargs = kwargs.copy()
        used_kwargs.update(zip(argspec.args[positional_count:], args[positional_count:]))
        print 'function %s called with positional args %s and keyword args %s' % (
            fn.__name__, args[:positional_count], 
            {k: used_kwargs.get(k, d) for k, d in defaults.items()})
        return fn(*args, **kwargs)
    return wrapper

This determines what keyword paramaters were actually used from both the positional arguments passed in, and the keyword arguments, then pulls out default values for those not used.

Demo:

>>> square(39)
function square called with positional args (39,) and keyword args {'trial': True, 'output': False}
no output
1521
>>> square(39, False)
function square called with positional args (39,) and keyword args {'trial': False, 'output': False}
no output
>>> square(39, False, True)
function square called with positional args (39,) and keyword args {'trial': False, 'output': True}
>>> square(39, False, output=True)
function square called with positional args (39,) and keyword args {'trial': False, 'output': True}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...