Adam
Adam

Reputation: 3795

What is the most pythonic way to call a method with optional parameters?

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.

Upvotes: 3

Views: 181

Answers (5)

John Kugelman
John Kugelman

Reputation: 361730

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

Upvotes: 7

Jon Clements
Jon Clements

Reputation: 142176

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!

Upvotes: 1

Thijs van Dien
Thijs van Dien

Reputation: 6616

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).

Upvotes: 3

tdelaney
tdelaney

Reputation: 77347

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=''))

Upvotes: 2

inspectorG4dget
inspectorG4dget

Reputation: 113985

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)

Upvotes: 2

Related Questions