Kevin Lin
Kevin Lin

Reputation: 23

Is it possible to pass the same optional arguments to multiple functions?

I want to ask if there is a way to prevent unnecessary duplicate of code when passing the same arguments into a function's optional arguments.

Hopefully the following example provides a good idea of what I am trying to do:

def f(arg1):
    def g(optional_1=0, optional_2=0, optional_3=0):
        return arg1+optional_1+optional_2+optional_3
    return g

b, c = 2, 3
f1 = f(1)
f2 = f(2)
calc_f1 = f1(optional_2=b, optional_3=c)
calc_f2 = f2(optional_2=b, optional_3=c)

As you can see, f1 and f2 only differ in the arg1 passed into f and afterwards I call them with the same variables for the same optional arguments. It is fine when the code is short, but when I have over 10 optional arguments, it becomes unnecessarily long and redundant. Is it possible to do something like

optional_variable_pair = #some way to combine them
calc_f1 = f1(optional_variable_pair)
calc_f2 = f2(optional_variable_pair)

so I get a more succinct and easy to read code?

Upvotes: 2

Views: 2285

Answers (3)

Andrew Palmer
Andrew Palmer

Reputation: 3013

To answer the question you asked, the answer is yes. You can do almost exactly what you want using keyword argument unpacking.

def f(arg1):
    def g(optional_1=0, optional_2=0, optional_3=0):
        return arg1+optional_1+optional_2+optional_3
    return g

optional_variable_pair = {
    'optional_2': 2,
    'optional_3': 3
}

f1 = f(1)
f2 = f(2)
calc_f1 = f1(**optional_variable_pair)
calc_f2 = f2(**optional_variable_pair)

If I'm reading your intent correctly, though, the essence of your question is wanting to pass new first arguments with the same successive arguments to a function. Depending on your use case, the wrapper function g may be unnecessary.

def f(arg1, *, optional_1=0, optional_2=0, optional_3=0):
    return optional_1 + optional_2+optional_3

optional_variable_pair = {
    'optional_2': 2,
    'optional_3': 3
}

calc_f1 = f(1, **optional_variable_pair)
calc_f2 = f(2, **optional_variable_pair)

Obviously, if the first argument continues incrementing by one, a for loop is in order. Obviously, if you are never using the optional_1 parameter, you do not need to include it. But, moreover, if you find yourself using numbered arguments, there is a good chance you really should be working with tuple unpacking instead of keyword unpacking:

def f(*args):
    return sum(args)

optional_variable_pair = (2, 3)

for i in range(1, 3):
    calc = f(i, *optional_variable_pair)
    # ...do something with calc...

You may also be interested in researching functools.partial, as well, which can take the place of your wrapper function g, and allow this:

import functools

def f(*args):
    return sum(args)

f1 = functools.partial(f, 1)
f2 = functools.partial(f, 2)

calc_f1 = f1(2, 3) # = 1 + 2 + 3 = 6
calc_f2 = f2(2, 3) # = 2 + 2 + 3 = 7

Upvotes: 1

diabolist
diabolist

Reputation: 4099

Any function with multiple optional arguments is a bit smelly because:

  1. you get so many argument combinations that it requires a large amount of testing.
  2. because of all the options the function has to have alot of conditionals and routes which increase its cyclomatic complexity.

You can apply a refactoring to extract the whole argument list into an Object and have the function work on that object. This works really well if you can find a unifying name that describes your argument list and fits whatever metaphor you are using around the function. You can even invert the call so that the function becomes a method of the Object, so you get some encapsulation.

Upvotes: 2

SatanDmytro
SatanDmytro

Reputation: 537

You use key-value pairs as function argsuments, for this purpose you can use *args and **kwargs:

optional_variable_pair = {
  "optional_1": 1,
  "optional_2": 2,
  "optional_3": 3,
}
calc_f1 = f1(**optional_variable_pair)
calc_f2 = f2(**optional_variable_pair)

Upvotes: 0

Related Questions