1

Let's say I have a function f() which I know accepts 1 argument, action, followed by a variable number of arguments.

Depending on the initial action value, the function expects a different set of following arguments, e.g. for action 1 we know we must have 3 extra arguments p1, p2 and p3. Depending on how the function was called, these args can be either in args or `kwargs.

How do I retrieve them?

def f(action, *args, **kwargs):
    if action==1:
    #we know that the function should have 3 supplementary arguments: p1, p2, p3
    #we want something like:
    #p1 = kwargs['p1'] OR args[0] OR <some default> (order matters?)
    #p1 = kwargs['p1'] OR args[0] OR <some default>
    #p1 = kwargs['p1'] OR args[0] OR <some default>

Note that:

f(1, 3, 4, 5)
f(1,p1=3, p2=4, p3=5)
f(1, 2, p2=4, p3=5)

will place the different p1, p2, p3 parameters in either args or kwargs. I could try with nested try/except statements, like:

try:
    p1=kwargs['p1']
except:
    try:
        p1=args[0]
    except:
        p1=default

but it does not feel right.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
user1159290
  • 951
  • 9
  • 27
  • It seems like you should make it `def f(action, p1=, p2=, p3=, **kwargs):` Or maybe `def f(action, *, **kwargs):` so _only_ the first parameter is positional and you can just use e.g. `p1 = kwargs.get("p1", )`. – jonrsharpe Nov 05 '22 at 22:36
  • p1, p2, p3 are not required for other actions. not sure about the meaning of your second suggestion... are further positional parameter not allowed then?? – user1159290 Nov 05 '22 at 22:43
  • Some context might help, your current design seems very complicated. And see https://stackoverflow.com/q/14301967/3001761. – jonrsharpe Nov 05 '22 at 22:45
  • The context is complex, but the question remains... is there an equivalent to the get function fur tupple? one could then write p1=args.get(0,kwargs.get('p1', default)) – user1159290 Nov 05 '22 at 22:53
  • I guess you _could_ do something like `args_d = dict(enumerate(args))` and then do the nested `get` with default. Or just use a combination of ternary `... if ... else` with `kwargs.get`. – tobias_k Nov 05 '22 at 22:57
  • You could also bind the passed parameters to a signature, e.g. like https://stackoverflow.com/a/72333599/3001761. – jonrsharpe Nov 05 '22 at 22:59

3 Answers3

3

I would call a sub-function with a defined argument list within f:

def f(action, *args, **kwargs):
    if action == 1:
        f_1(*args, **kwargs)
    ...

def f_1(p1, p2, p3):
    # now you know the args

Of course, this raises the question why you need the over-all function, but let's assume you have a reason for that ;). Else, you could also have a dictionary:

funcs = {1: f_1, 2: ....}

Then you can call the correct function with individual arguments as

funcs[action](p1, p2, p3)
Dr. V
  • 1,747
  • 1
  • 11
  • 14
2

I feel you were quite close to the answer on your question in out of itself. You can do something like this:

def f(action, *args, **kwargs):
     if action==1:
     p1=kwargs.get('p1') or args[0] if len(args)>0 else 'default'
     ...

The order does matter. In this case if you the key arguments would take priority over positional ones.

Here are some input examples --> value of p1 in the function:

  • f(1,10)-->10
  • f(1,2,20)-->2
  • f(1,p1=10)-->10
  • f(1,10,p1=2)-->2
  • f(1,p3=20)-->'default'
juanfe888
  • 21
  • 2
  • Isn't this going to raise an error when arg[0] is not defined...? hence pushing me in the nested try/except statement I tried to avoid? – user1159290 Nov 06 '22 at 00:03
  • That would be the case if you passed no args nor kwargs. Just edited the answer in case that happens. However as you apply this to higher indices in the args you would have to augment the condition value to be sure no error happens. – juanfe888 Nov 06 '22 at 00:34
1

You can try with, using OrderedDict:

from collections import OrderedDict


def f(action, *args, **kwargs):
    params = OrderedDict({
        'p1': 'default',
        'p2': 'default',
        'p3': 'default',
    })

    if action == 1:
        for index, param in enumerate(params.keys()):
            if index < len(args):
                params[param] = kwargs[param] if param in kwargs else args[index]
            else:
                params[param] = kwargs[param] if param in kwargs else params[param]

        # Do something with `params`.


f(1, 3, 4, 5)
f(1, p1=3, p2=4, p3=5)
f(1, 2, p2=4, p3=5)

If you need more parameters, just add them to the ordered dictionary.

Iliya
  • 432
  • 1
  • 4
  • 9