Require parameter if another argument is present

Viewed 130

Following is an example of what I would like to do.

def f(a, b, c):
    if a == 'method1':
        c = 0
    return b + c

In this function, parameter c is unneeded if the condition a='method1' is satisfied. Still, I can call the function with f(a='method1', b=1, c=2), which will have the same effect as f(a='method1', b=1, c=0).

A solution to this is to set the parameter c default to 0

def f(a, b, c=0):
    if a == 'method1':
        c = 0
    return b + c

Now I can call f(a='method1',b=1), which is exactly what I want. The problem is, I can still change the parameter c in the call f(a='method1',b=1,c=1), which I do not want the user to be able to.

Can I enforce this condition in the function signature, and not in the body (i.e. I would not like to use if in the body). Or if there is another better solution, please tell.

Something like

def f(a, b, c = 0 if a == 'method1', else c is required):
    return b + c

Thanks in advance.

2 Answers

Hmm... this almost seems like something that you should be able to do with functools.singledispatch and typing.Literal, but I can't quite get them to work together at least in python 3.7 (with Literal coming from the typing_extensions module). I think that in general singledispatch is probably the only tool that will really get what you've asked for as the different registered functions can have entirely different signatures. However, to do this our methods need to be different classes.

from functools import singledispatch

class Method1():
    pass

class OtherMethods():
    pass

@singledispatch
def f(method, b, c):
    return b + c

@f.register(Method1)
def _(method, b):
    return b

f(Method1(), 12)  # returns 12
f(Method1(), 12, 7) # raises an error
f(OtherMethods(), 12) # raises an error
f(OtherMethods(), 12, 7) # returns 19

Now this is not exactly what you asked for but the arguments are enforced in the signature.

If someone who knows more about the implementation of singledispatch and Literal comes by maybe they can explain the interaction between the two.

One easier thing to do would be to simply define c to default to some invalid value.

def f(a, b, c=None):
    if a == 'method1':
        c = 0
    return b + c

This solves the problem that if the user forgets to set c for a method besides method1 they'll receive a (possibly cryptic) error message. However it doesn't fix the fact that if they set c when using method1 that value will be silently ignored and possibly cause confusion.

a, b and c are all assigned dynamically at runtime. There is no way you can make up for this in the signature. It needs to be detected at runtime and you might as well do that in an if as anywhere else. You can specialize at the function name level, though, and python will take care of detecting the number of parameters.

def f(b,c):
    return b + c

def f_method1(b):
    return f(b, 0)

def f_method2(half_a_c):
    return f(0, half_a_c*2)
Related