Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the keyword arguments actually passed to a Python method

I'm dreaming of a Python method with explicit keyword args:

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)

I want to get a dictionary of only those arguments the caller actually passed into the method, just like **kwargs, but I don't want the caller to be able to pass any old random args, unlike **kwargs.

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

So: is there such an incantation? In my case, I happen to be able to compare each argument against its default to find the ones that are different, but this is kind of inelegant and gets tedious when you have nine arguments. For bonus points, provide an incantation that can tell me even when the caller passes in a keyword argument assigned its default value:

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

Tricksy!

Edit: The (lexical) function signature has to remain intact. It's part of a public API, and the primary worth of the explicit keyword args lies in their documentary value. Just to make things interesting. :)

like image 401
Doctor J Avatar asked Sep 11 '09 03:09

Doctor J


People also ask

How do you get keyword arguments in Python?

Embrace keyword arguments in Python Consider using the * operator to require those arguments be specified as keyword arguments. And remember that you can accept arbitrary keyword arguments to the functions you define and pass arbitrary keyword arguments to the functions you call by using the ** operator.

How do you pass keyword arguments?

Kwargs allow you to pass keyword arguments to a function. They are used when you are not sure of the number of keyword arguments that will be passed in the function. Kwargs can be used for unpacking dictionary key, value pairs. This is done using the double asterisk notation ( ** ).

How do you pass an argument to a function in Python?

Information can be passed into functions as arguments. Arguments are specified after the function name, inside the parentheses. You can add as many arguments as you want, just separate them with a comma.

What Python data construct is used to pass keyword arguments to functions?

Using the Python kwargs Variable in Function Definitions **kwargs works just like *args , but instead of accepting positional arguments it accepts keyword (or named) arguments.


3 Answers

I was inspired by lost-theory's decorator goodness, and after playing about with it for a bit came up with this:

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 

Which prints this:

 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' 

I'm happy with this. A more flexible approach is to pass the name of the attribute you want to use to the decorator, instead of hard-coding it to 'actual_kwargs', but this is the simplest approach that illustrates the solution.

Mmm, Python is tasty.

like image 189
Doctor J Avatar answered Sep 24 '22 01:09

Doctor J


Here is the easiest and simplest way:

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

This give the output: {'a': 2, 'c': None, 'b': 'egg'}. The reason args should be a copy of the locals dictionary is that dictionaries are mutable, so if you created any local variables in this function args would contain all of the local variables and their values, not just the arguments.

More documentation on the built-in locals function here.

like image 41
Isaiah Avatar answered Sep 20 '22 01:09

Isaiah


One possibility:

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

IOW, it's very easy to check that the passed-in names are within the acceptable set and otherwise raise whatever you'd want Python to raise (TypeError, I guess;-). Pretty easy to turn into a decorator, btw.

Another possibility:

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

by making a unique object _sentinel you remove the risk that the caller might be accidentally passing None (or other non-unique default values the caller could possibly pass). This is all object() is good for, btw: an extremely-lightweight, unique sentinel that cannot possibly be accidentally confused with any other object (when you check with the is operator).

Either solution is preferable for slightly different problems.

like image 45
Alex Martelli Avatar answered Sep 24 '22 01:09

Alex Martelli