5

I have a function which takes a lot of parameters, and since I dont want to remember their positions I decided to use named arguments

def f(a=None, b=None, c=None):
    print a,b,c
f('test', c=5, b='second param')
>>> test second param 5

now, usually I only change one parameter at a time, so i want to call the function by only typing f(c=3.14) with the intended result being f(a=a, b=b, c=3.14), namely every argument that is not explicitely passed should be read from the local scope.

But of course it doesnt work since to use named arguments I have to set a default value, if I use **kwargs instead, it simply ignores the arguments

def f(**kwargs):
    print a,b,c
a=1; b=2; c=3
f(a=2, b=4, c=8)
>>> 1 2 3 # instead of 2 4 8

I used to define a new function for each parameter but I dont like this approach although it has been the most effective so far

def fc(c_new):
    return f(a, b, c_new)

How do I make the function use the variables in its current scope as default values for its named arguments?

3
  • You are not using keyword arguments within the function! Commented Nov 27, 2013 at 11:58
  • @volcano That's the point. Commented Nov 27, 2013 at 12:05
  • All function arguments have names. I think you mean keyword arguments. Commented Nov 27, 2013 at 12:33

3 Answers 3

9

Here is a solution with a decorator:

from functools import wraps
from inspect import getcallargs


def defaults_from_globals(f):
    @wraps(f)
    def new(**kwargs):
        # filter only those vars, that are in the list of function's named args
        from_globals = {arg: globals()[arg] for arg in getcallargs(f)}
        # overwrite them with user supplied kwargs
        from_globals.update(kwargs)
        return f(**from_gobals)

    return new


@defaults_from_globals
def f(a=None, b=None, c=None):
    return a, b, c


a = 1
b = 2
c = 3

print f(a=2, b=4) # 2 4 3
Sign up to request clarification or add additional context in comments.

5 Comments

Took me a while to figure out what is the actual goal though).
It could potentially result in a lot less overhead to only retrieve globals with the same name as keyword arguments instead of the other way around as you're doing. i.e. globals_ = globals(), from_globals = {k: globals_[k] for k in getcallargs(f) if k in globals_}. Good answer regardless. +1
That does exactly what i meant and even more, since it can be quickly applied to any function (although I dont feel comfortable using the secret arts of decorators, they are just, ...weird).
Ones you've understand them (decorators), you'll get an enormously powerful tool.
The way you incorporated my suggestion into your answer could fail if there's no matching global identifier for a keyword argument (which is why I put that if at the end of the dictionary comprehension in my comment). Yours also potentially calls globals() for each key word argument, which is wasteful.
1

For the sake of completeness here is one more solution I just found, it's more of a dirty hack, because the use of exec is frowned upon in most cases* but it's more straightforward to understand.

*) in this case the user has control over the code and it is not in his interest to intentionally screw up his own work

def f(x=None, y=None, z=None):
    for  key,val in locals().items():
        if val==None: exec("%s = globals()[key]"%key)
    print x,y,z

Comments

0

i think you should use decorators

import functools

_last_args = {}
def foo(f):
    @functools.wraps(f)
    def wrapper( *args, **kwargs):
        _last_args.update(kwargs)
        ret =  f(*args, **_last_args)
        return ret
    return wrapper

@foo
def bar(a=None,b=None,c=None):
    print(a,b,c)

bar(a=1,b=2,c=3)
bar(a=10,b=20)
bar(a=100)

prints:

1 2 3
10 20 3
100 20 3

2 Comments

Good call on using a decorator, but that's not what the OP wants
i think when he says scope he means to get the previous ones because if it was only default values in the scope he would save them as default in the function instead of None. Maybe wrong

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.