views:

135

answers:

4

I don't want to use *args or **kwargs since I can't change function declaration.

For example:

def foo( a, b, c ) """Lets say values passed to a, b and c are 1,2 and 3 respectively"""
   ...
   ...
   """ I would like to generate an object preferably a dictionary such as {'a':1, 'b':2, 'c':3} """
   ...
   ...

Can anyone suggest a way to do this? Thanks in advance.

+6  A: 

If you can't change the function "declaration" (why not?) but you can change the contents of the function, then just create the dictionary as you want it:

def foo(a, b, c):
    mydict = {'a': a, 'b': b, 'c': c}

If that doesn't work, I think you need a better explanation of what you want and what the constraints are in your case.

This is also going to give similar results in the above case (where you don't show any local variables other than the arguments), but be warned that you should not try to modify locals():

def foo(a, b, c):
    mydict = locals()
Peter Hansen
Could not a copy of the `locals()` dict solve the issue, i.e. `mydict = locals().copy()`? At least the issue about not modifying `mydict`?
ndim
@ndim, yes, that would work fine as far as avoiding the problem of modifying locals(). Whether it would help the OP we can't know, based on the little he gave so far.
Peter Hansen
>>>>>> def foo(a, b, c):... mydict = locals()... mydict['a']=0... print mydict... print a... >>> >>> foo(1,2,3){'a': 0, 'c': 3, 'b': 2}1>>> looks like I just modified mydict.. and it didnt throw any errors !!
Rohit
Please read the documentation I referenced. It doesn't say changes *will not* work, it says "may not". Do you want to gamble?
Peter Hansen
@Peter hmm... modifying mydict wont modify local variables in runtime... mydict is just a snapshot of local vars at the time it was called and its just another dictionary so it can be modified.
Rohit
@Rohit, it's clearly not "just" another dictionary, since sometimes changes *do* affect the local names (as I recall). If you are sure you've fully understood the issues and implications, though, by all means go ahead and use it (and change it). I just think of it as an unreliable "view" on the locals (all locals, not just the arguments) that is suitable for passing as a dict argument for % formatting of strings, and nothing much else.
Peter Hansen
See also things like http://stackoverflow.com/questions/1450275 for more background.
Peter Hansen
A: 
def foo(a, b, c):
  args = {"a": a, "b": b, "c": c}
Roger Pate
I want this for any general function.
Rohit
There is probably a better way than what you asked to achieve what you really want to do. (An object for all parameters is a means, not an end.) Until you ask about what you really want, there's not much we can do to help.
Roger Pate
+1  A: 

@Rohit, we do not understand what you mean when you say "the function declaration". If you mean you don't want to change the API of the function (the documented way the function is called), perhaps because you have existing code already calling an existing function, then you can still use the **kwargs notation, and the callers will never know:

def foo(a, b, c):
    return a + b + c

def foo(**kwargs):
    total = 0
    for x in ("a", "b", "c"):
        assert x in kwargs
        total += kwargs[x]
    return total

def bar():
    foo(3, 5, 7)

bar() cannot tell which version of foo() it is calling, and does not care.

Perhaps you are looking for a "wrapper" you can wrap around existing function objects, without changing the actual source code of the function object?

def make_wrapper(fn, *arg_names):
    def wrapped_fn(*args):
        mydict = dict(tup for tup in zip(arg_names, args))
        print("TEST: mydict: %s" % str(mydict))
        return fn(*args)
    return wrapped_fn


def foo(a, b, c):
    return a + b + c

foo = make_wrapper(foo, "a", "b", "c")

foo(3, 5, 7)

The new wrapped function gathers the arguments into mydict and prints mydict before calling the function.

steveha
@steveha Thanks for the details.. and yes actually I want a wrapper function, but I don't want to add a call to make_wrapper each time I add a new function.. I will go with a decorator that would collect all input arguments in a dictionary... with "locals()" as the first statement in the decorator function.
Rohit
I am not sure how to write the decorator you want. If you use `*args` to collect the arguments in the wrapper function, you do not get the names of the arguments (that is why I required you to pass the argument names to the `make_wrapper()` function). What you really want is a snapshot of the result of calling `locals()` from *inside* the wrapped function; you can't get this before the function is called or after it returns. Maybe the perfect solution would require hacking Python bytecodes! There might be something in the Python Cookbook.
steveha
A: 

By diligent searching of StackOverflow, I found out how to do this. You use the inspect module.

import inspect

def make_wrapper(fn):
    arg_names = inspect.getargspec(fn)[0]
    def wrapped_fn(*args, **kwargs):
        # mydict now gets all expected positional arguments:
        mydict = dict(tup for tup in zip(arg_names, args))
        # special name "__args" gets list of all positional arguments
        mydict["__args"] = args
        # mydict now updated with all keyword arguments
        mydict.update(kwargs)
        # mydict now has full information on all arguments of any sort
        print("TEST: mydict: %s" % str(mydict))
        return fn(*args, **kwargs)
    return wrapped_fn

def foo(a, b, c, *args, **kwargs):
    # a, b, and c must be set; extra, unexpected args will go in args list
    return a + b + c

foo = make_wrapper(foo)

foo(3, 5, 7, 1, 2)
# prints: TEST: mydict: {'a': 3, 'c': 7, 'b': 5, '__args': (3, 5, 7, 1, 2)}
# returns: 15

There you go, a perfect solution to the problem you stated. It is a wrapper, you don't need to pass in the arguments, and it should work for any function. If you need it to work with class objects or something you can read the docs for inspect and see how to do it.

Note, of course order is not preserved in dictionaries, so you may not see the exact order I saw when I tested this. But the same values should be in the dict.

steveha