简体   繁体   中英

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!

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)

A common approach to avoid many if - elif s 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 lambda s 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=...)

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.

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 eg 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 (eg relations(b=2, c=5) only b will be used and c is ignored).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM