61

I have a function with two optional parameters:

def func(a=0, b=10):
    return a+b

Somewhere else in my code I am doing some conditional argument passing like:

if a and b:
   return func(a, b)
elif a:
   return func(a)
elif b:
   return func(b=b)
else:
   return func()

Is there anyway to simplify code in this pattern?

EDIT:

Let's assume that I'm not allowed to implement default parameter logic inside func.

I may have several functions like func: func1, func2 and func3 would all contain the

a = a or 0
b = b or 10

statements.

But I'm invoking these series of functions to eliminate duplication. (using a decorator)

2
  • if b == 0 by intention this could cause an error. Commented Jun 25, 2012 at 8:45
  • Move the pattern inside the function? Commented Jun 26, 2012 at 8:54

9 Answers 9

88

If you don't want to change anything in func then the sensible option would be passing a dict of arguments to the function:

>>> def func(a=0,b=10):
...  return a+b
...
>>> args = {'a':15,'b':15}
>>> func(**args)
30
>>> args={'a':15}
>>> func(**args)
25
>>> args={'b':6}
>>> func(**args)
6
>>> args = {}
>>> func(**args)
10

or just:

>>>func(**{'a':7})
17
Sign up to request clarification or add additional context in comments.

6 Comments

As often with Python questions, the best answer is the only sensible one!
This approach works and is Pythonic, but it doesn't come without risk. Since the keys in the dictionary are string representation of variable names, there's a risk that the variable name in the key might not match the actual variable name, and the bug that would introduce could be tricky to hunt down. This could either happen because of a typo in the key name (think "is_illogical" vs "is_iliogical") or missing the key name when renaming a variable.
Does this really solve the problem of avoiding if-else statements? With this, you would still need to have if-else statement to define the args dict, correct?
@Sapience - you still get if statements, but you get just one per variable parameter, and you don't replicate all the other parameters inside each of the if/else branches. Think of a function that takes 6 parameters, of which 3 may or may not be present. I can use 3 if statements to build up the dict, then have one function call. Without this technique, I'd have a combinatorial explosion as I added optional parameters.
Genius, you saved my day
|
4

Going by the now-deleted comments to the question that the check is meant to be for the variables being None rather than being falsey, change func so that it handles the arguments being None:

def func(a=None, b=None):
   if a is None:
      a = 0
   if b is None:
      b = 10

And then just call func(a, b) every time.

Comments

4

You can add a decorator that would eliminate None arguments:

def skip_nones(fun):
    def _(*args, **kwargs):
        for a, v in zip(fun.__code__.co_varnames, args):
            if v is not None:
                kwargs[a] = v
        return fun(**kwargs)
    return _

@skip_nones
def func(a=10, b=20):
    print a, b

func(None, None) # 10 20
func(11, None)   # 11 20
func(None, 33)   # 10 33

Comments

1

to solve your specific question I would do:

args = {'a' : a, 'b' : b}
for varName, varVal in args.items():
    if not varVal:
        del args[varName]
f(**args)

But the most pythonic way would be to use None as the default value in your function:

f(a=None, b=None):
    a = 10 if a is None else a
    ...

and just call f(a, b)

Comments

1

By default, all methods in Python take variable arguments.

When you define an argument in the signature of the method, you explicity make it required. In your snippet, what you are doing is giving it a default value - not making them optional.

Consider the following:

>>> def func(a,b):
...    a = a if a else 0
...    b = b if b else 10
...    return a+b
...
>>> a = b = None
>>> func(a,b)
10
>>> a = 5
>>> b = 2
>>> func(a,b)
7
>>> func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (0 given)

In this snippet, both a and b are required, since I didn't define any default values. They are not optional.

However in your snippet, because you have given them defaults in the method signature they look like they are optional, but in fact they just have defaults assigned to them.

>>> def func(a=0,b=10):
...    return a+b
...
>>> func()
10

You can confirm this by checking the argument list inside the body of the method:

>>> def func(a=b,b=10):
...    print locals().keys()
...
>>> func()
['a', 'b']

One way to have your function accept any number of arguments (in other words, making them all optional):

>>> def func(*args,**kwargs):
...    print len(args),args
...    print len(kwargs),kwargs
...
>>> func("hello","world",a=5,b=10)
2 ('hello', 'world')
2 {'a': 5, 'b': 10}
>>> func()
0 ()
0 {}
>>> func(1,2)
2 (1, 2)
0 {}
>>> func(a)
1 (5,)
0 {}
>>> func(foo="hello")
0 ()
1 {'foo': 'hello'}

4 Comments

why not replace a if a else 0 with a or 0, same with 10?
@unkulunkulu: You could argue for and against both his and my versions. His is more explicit but more verbose, mine is shorter and (arguably) simpler, but it requires knowledge of how or short-circuits.
@TimPietzcker, I thought this knowledge runs in the blood of a python programmer, maybe I'm wrong, because I'm quite new to the language.
One could argue explicit better than implicit especially in the context of explaining things; but then I probably should have written out the "long" version.
0

Why not pass that logic to the function?

def func(a, b):
    a = a or 0
    b = b or 10
    return a + b

3 Comments

and probably make default values Nones or something
@unkulunkulu: I don't think so. The (original) function doesn't appear to be called with variable argument lists, or else we could simply have done def func(a=0, b=10): and be done with it. Satoru.Logic is always passing two values to the function, but they may be None.
@moooeeeep: No - his original logic treats 0 and None exactly the same way.
0

To answer your literal question:

func(a or 0, b or 10) 

Edit: See comment why this doesn't answer the question.

2 Comments

This solution has been proposed before and retracted because it duplicates the default values on the caller's side.
@TimPietzcker OK. I probably misunderstood what constraints are for which values to go where. I'll leave the answer here so others won't make the same mistake.
0

You can use the ternary if-then operator to pass conditional arguements into functions https://www.geeksforgeeks.org/ternary-operator-in-python/

For example, if you want to check if a key actually exists before passing it you can do something like:

 
    def func(arg):
        print(arg)

    kwargs = {'name':'Adam')

    func( kwargs['name'] if 'name' in kwargs.keys() else '' )       #Expected Output: 'Adam'
    func( kwargs['notakey'] if 'notakey' in kwargs.keys() else '' ) #Expected Output: ''
 

Comments

-1

This might work:

def f(**kwargs):
    a = get(kwargs, 0)
    b = get(kwargs, 10)
    return a + b

3 Comments

What makes you think that this might work? :) If you call f() with any number of parameters, you get a TypeError.
@TimPietzcker well it does work, but S/O suddenly decided to throw a lot of updates at me - which when read make this answer invalid in the context of the OP's quesiton
@TimPietzcker (ie, it worked in the context I had available at the time of reading) - I think I should just delete this...

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.