Question

Let's say I have a method with a few optional parameters.

def foo(a, b=1, c=2, d=3)

How do I go about calling it so that if my variables are None or empty strings the defaults are used?

Conditionals like the following seems like a horrible solution:

if b and not c and d:
    foo(myA, b = myB, d = myD)
elif b and not c and not d:
    ...

In Java I'd jump for a factory, but it seems like that's what defaults are supposed to avoid in this case.

Was it helpful?

Solution

I would change foo so it replaces empty values with default ones.

def foo(a, b=None, c=None, d=None):
    if not b: b = 1
    if not c: c = 2
    if not d: d = 3

Note that this will treat all "false-y" values as defaults, meaning not only None and '' but also 0, False, [], etc. Personally I would tighten the interface up and use None and only None as a default value.

def foo(a, b=None, c=None, d=None):
    if b is None: b = 1
    if c is None: c = 2
    if d is None: d = 3

OTHER TIPS

Though I agree that changing the method is a better idea, here's an alternative that changes the calling part by using a dict of arguments, which is filtered and then unpacked:

d = {'b': myB, 'd': myD}
foo(myA, **{k: d[k] for k in d if d[k]})

Of course if d[k] can be replaced by if d[k] not in {None, ''} for example, which has a slightly different meaning (as pointed out by others).

If you want to catch ONLY None and '':

def foo(a, b, c, d):
    blacklist = set([None, ''])
    if b in blacklist:
        b = 1
    if c in blacklist:
        c = 2
    if d in blacklist:
        d = 3

If you want to catch all values v such that bool(v) is False, then:

def foo(a, b, c, d):
    b = b or 1
    c = c or 2
    d = d or 3

Or you could decorate the function with another function that does the assertions for you (which may or may not be overkill, based on your use case)

You could call a function that filters out the variables you don't want passed down

def arg_filter(**kw):
    return dict((k,v) for k,v in kw.items() if v not in (None, ''))

foo(**arg_filter(a=1,b=None,c=''))

Not fully tested, but should act as a base:

import inspect
from functools import wraps

def force_default(f):
    @wraps(f)
    def func(*args, **kwargs):
        ca = inspect.getcallargs(f, *args, **kwargs)
        da = inspect.getargspec(f)
        dv = dict(zip(reversed(da.args), reversed(da.defaults)))
        for k, v in ca.iteritems():
            if v is None or v == '':
                ca[k] = dv[k]
        return f(**ca)
    return func

@force_default
def foo(a, b=1, c=2, d=3):
    print a, b, c, d

foo(6, '', None, 'rabbit!')
# 6 1 2 rabbit!
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top