• 3

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191


File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

Pythonic solution for conditional arguments passing

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)
   return func()

Is there anyway to simplify code in this pattern?


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


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

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)
>>> args={'a':15}
>>> func(**args)
>>> args={'b':6}
>>> func(**args)
>>> args = {}
>>> func(**args)

or just:

  • 39
Reply Report
      • 1
    • 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?
      • 1
    • @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.
      • 1
    • 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.

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.

  • 3
Reply Report

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 _

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

func(None, None) # 10 20
func(11, None)   # 11 20
func(None, 33)   # 10 33
  • 2
Reply Report

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]

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)

  • 1
Reply Report

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)
>>> a = 5
>>> b = 2
>>> func(a,b)
>>> 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()

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'}
  • 1
Reply Report

Why not pass that logic to the function?

def func(a, b):
    a = a or 0
    b = b or 10
    return a + b
  • 0
Reply Report
    • @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.

To answer your literal question:

func(a or 0, b or 10) 

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

  • 0
Reply Report
    • 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.

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

    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: ''
  • 0
Reply Report

This might work:

def f(**kwargs):
    a = get(kwargs, 0)
    b = get(kwargs, 10)
    return a + b
  • -1
Reply Report
      • 2
    • What makes you think that this might work? :) If you call f() with any number of parameters, you get a TypeError.
      • 2
    • @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
      • 2
    • @TimPietzcker (ie, it worked in the context I had available at the time of reading) - I think I should just delete this...

Trending Tags