0

I want to make a function that is flexible with regard to unpacking the number of input variables.

More specifically, for example I have the following:

def flexi_func(vars):
    func_var_a, func_var_b, func_var_c, func_var_d = vars
    #do something

my_vars = [var_a, var_b, var_c, var_d]
flexi_func(my_vars)

This works fine if the number of input variables is 4. But say I want to have the same function operate on just three input variables, or two. Assume the 'do something' bit is already flexible. Then to unpack the variables I can write

def flexi_func(vars):
    if len(vars) == 4:
        func_var_a, func_var_b, func_var_c, func_var_d = vars
    elif len(vars) == 3:
        func_var_a, func_var_b, func_var_c = vars
    elif len(vars) == 2:
        func_var_a, func_var_b = vars
    #do something

And this works fine too. It just seems a bit clunky to me, especially if I had N>4 variables. Is there a cleaner, more Pythonic way to unpack a tuple in such a way that I can use the unpacked variables?

I know from this question I can do something like this (in Python 3):

foo, bar, *other = func()

but I would need to do more work to access the stuff in other, so it's no better than my if...elif... approach.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
berkelem
  • 2,005
  • 3
  • 18
  • 36
  • 2
    You'd have to do more work to determine whether `func_var_c` and `func_var_d` are even usable, so I'm not sure what you're expecting to accomplish by "simplifying" things here. I suppose you could come up with a global default like `None`, then do `func_var_a, func_var_b, func_var_c, func_var_d = [*vars, None, None][:4]` and know every local name has an assigned value, but you're still stuck with `is None` tests later. – ShadowRanger Aug 30 '18 at 00:25
  • A list serves the exact purpose of *containing an arbitrary amount of things*, you should not be using something else, or maybe a dictionary. If you want to access items in the list, just use indexing, May we ask what is the bigger picture? What are you trying to accomplish? – Olivier Melançon Aug 30 '18 at 00:28
  • 1
    What do you actually want to _do_ with these variables? Are you just looking for some kind of default values you can use? If so, you can always write, e.g., `a, b, c, d, *_ = vars + [None]*4` (in other words, pad out `vars` to be at least 4 values long by filling with `None`, then unpack the first 4 values and ignore anything left over). – abarnert Aug 30 '18 at 00:31
  • But really, if `d` either does or doesn't exist based on what was passed in, most likely your logic is going to have to switch on the same thing anyway, maybe even multiple times,, using `d` when `len(vars) == 4` but not using it otherwise. If you can turn this from an abstract toy example into one that does at least something trivial with the variables, we can probably show you a better way to do it. – abarnert Aug 30 '18 at 00:33

1 Answers1

0

Firstly, in response to the comments asking for context or a concrete example where this might be necessary, I concede that in almost all the cases I can think of it should be possible to avoid unpacking altogether. This goes for my original problem too - the do something part of the function can be modified to access the items through list indices.

However, in principle, I am sure there exist situations where unpacking is needed, even if only for clarity. Below is the best way I can find to assign a variable to each input item. More background detail is given in the answers here and here.

def flexi_func(var_list):    
    for i, var in enumerate(var_list):
        vars()['my_func_{}'.format(i)] = var

    #do something 

    return

This assigns each input variable to the vars() built-in dictionary. This is preferable to the globals() built-in dictionary and is writable unlike the locals() built-in dictionary. To access the variables in the do something section, you have to reference the dictionary like so: print(vars()['my_func_2']).

Finally, if you want to use letters as variable labels instead of numbers (as I did in my problem statement) you can add alphabet = [chr(i) for i in range(ord('a'),ord('z')+1)] to the top of the function and call the variables 'my_func_{}'.format(alphabet[i]).

berkelem
  • 2,005
  • 3
  • 18
  • 36
  • The `vars()` dictionary [*is* the `locals()` dictionary](https://docs.python.org/3/library/functions.html#vars): "Without an argument, `vars()` acts like `locals()`.". If you just need a dictionary, stop messing around and make a dictionary. Call it `vars` if you feel like being perverse; `vars = {}`, then `vars['my_func_{}'.format(i)] = var` works fine, and doesn't get involved with weird and horrible attempts to mutate `locals()` that don't actually create new local variables, but just make you feel like you did. – ShadowRanger Sep 08 '18 at 03:06