Wilco
Wilco

Reputation: 978

What’s the most Pythonic way of handling different, mutually-exclusive function inputs?

I am trying to find a clean way to handle different, mutually-exclusive function inputs. The idea is that I have a function which returns 4 values (these values are linked through mathematical equations), and when you input one of the 4 values, it returns all the values.

Currently the function works like this:

#example relations are simply: b=1+a, c=0.5*a, d=sqrt(a)
def relations(v, vtype="a"):
    if vtype=="a":
        a = v
    elif vtype=="b":
        a = v - 1
    elif vtype=="c":
        a = 2 * v
    elif vtype=="d":
        a = v ** 2

    b = 1 + a
    c = 0.5 * a
    d = a ** 0.5

    return a,b,c,d

The user specifies what the input variable is by means of a string vtype, and it returns all the values. It is not possible for the user to input more than one different input value (would be redundant, because all the unknowns can be determined from one input value).

Is there a more clean and pythonic way to do this? Specifying the input variable with a string feels really dirty at the moment.

Thanks in advance!

Upvotes: 1

Views: 433

Answers (4)

TobiMarg
TobiMarg

Reputation: 3817

You could use something like this:

def relations(a=None, b=None, c=None, d=None):
    if a is not None:
        pass
    elif b is not None:
        a = b - 1
    elif c is not None:
        a = 2 * c
    elif d is not None:
        a = d ** 2
    else:
        raise TypeError('At least one argument needed')

    # your calculations

Then you can use the function simply with e.g. relations(c=10) or relations(a=2). So you don't need the vtype argument.

The first passed argument is used to calculate a. If you call the function with more than one argument only the first will be used and the others will be ignored (e.g. relations(b=2, c=5) only b will be used and c is ignored).

Upvotes: 0

Bakuriu
Bakuriu

Reputation: 102039

A common approach to avoid many if-elifs is to build a dictionary of functions:

def relations(v, vtype='a'):
    functions = {
        'a': lambda x: x, 'b': lambda x: x-1,
        'c': lambda x: x * 2, 'd': lambda x: x**2
    }

    a = functions[vtype](v)
    b = 1 + a
    c = 0.5 * a
    d = a ** 0.5
    return a,b,c,d

If this function is not a bottleneck you can avoid using the lambdas and simply do:

values = {'a': v, 'b': v-1, 'c': v * 2, 'd': v**2}
a = values[vtype]

If you don't like the idea of having vtype in the function signature you can use a single **kwargs argument:

def relations(**kwargs):
    if len(kwargs) != 1 or not set('abcd').intersection(kwargs):
        raise ValueError('Invalid parameters')
    vtype, v = kwargs.popitem()
    functions = {
        'a': lambda x: x, 'b': lambda x: x-1,
        'c': lambda x: x * 2, 'd': lambda x: x**2
    }

    a = functions[vtype](v)
    b = 1 + a
    c = 0.5 * a
    d = a ** 0.5
    return a,b,c,d

Then call it as:

relations(a=...)
relations(b=...)

Upvotes: 2

E.Z.
E.Z.

Reputation: 6661

You can define your vtypes as functions, and pass the function as parameter

def a(v):
    return v

def b(v):
    return v-1

def c(v):
    return 2*v

def d(v):
    return v**2

def relations(v, vtype=a):
    value_a = vtype(v)
    value_b = 1 + value_a
    value_c = 0.5 * value_a
    value_d = value_a ** 0.5
    return value_a,value_b,value_c,value_d

With that you can get rid of the if/elif too.

Upvotes: 1

Ber
Ber

Reputation: 41873

You could use variable keyword arguments:

def relations(**kwargs):
    if 'a' in kwargs:
        a = kwargs['a']
    elif 'b' in kwargs:
        a = kwargs['b'] - 1
    elif 'c' in kwargs:
        a = kwargs['c'] * 2
    elif 'd' in kwargs:
        a = kwargs['d'] ** 2
    else:
        raise TypeError('missing an argument')

    b = 1 + a
    c = 0.5 * a
    d = a ** 0.5

    return a, b, c, d

Then use with named parameters:

relations(a=2)
relations(b=4)
relations(c=9)
relations(d=0)

Upvotes: 2

Related Questions