Optional parameters, a specific combination of them is required

I have a general question as well as a specific use case.

The optional parameters are simple enough: def func(a, b, c=None): ...

and then somewhere c can be used in the body, just write if c:

first or something along these lines. But what about when a certain combination of parameters is required? The general case is to consider any arbitrary situation in which precise parameters exist. For a function, def func(a, b, c=None, d=None, e=None, f=None): ...

this would include silly things like: provide c and d but not e and f, or provide e only, or provide at least 3 of c, d, e and f. But my use case does not require such generality.

For def func(a, b, c=None, d=None): ...

, I want EXACTLY ONE of c and d to be provided.

The solutions I was thinking include:
- in the body, manually check how many c and d are not equal to None, and if it is not exactly 1, return an error saying you need to specify exactly 1

eg.

def func(a, b, c=None, d=None):
    how_many_provided = len([arg for arg in [c, d] if arg]) # count the non-None optional args
    if not how_many_provided == 1:
        return "Hey, provide exactly 1 of 'c' and 'd'"
    if c:
        # stuff to do if c is provided
    elif d:
        # stuff to do if d is provided

      

- change the function as def func(a, b, e, f): ...

where e represents either c or d and f indicates which one represents e. ex.

def func(a, b, e, f):
    if f == 'c':
        # stuff to do if c is provided, with e as c
    if f == 'd':
        # stuff to do if d is provided, with e as d

      

This will work, but what is the standard / accepted / pythonic way to do it?

+1


source to share


3 answers


You can just use the args dict keyword:



def func(a, b, **kwargs):
  valid_args = len(kwargs) == 1 and ('c' in kwargs or 'd' in kwargs)

  if not valid_args:
      return "Hey, provide exactly 1 of 'c' and 'd'"
  if 'c' in kwargs:
      # stuff to do if c is provided
  elif 'd' in kwargs:
      # stuff to do if d is provided

      

+2


source


I would say that the easiest way for your user in your simple case is to refactor the individual functions. Each function does a different job as described and then the usual one, for example. for your last case

def funcC(a, b, c):
        # stuff to do if c is provided, with e as c
    common_func(a,b,c, None)

def funcD(a, b, d):
    # stuff to do if d is provided, with e as d
    common_func(a,b,None, d)

      

The user then knows which parameters are relevant and only valid possible combinations can be used, the user does not need to guess or be able to call them incorrectly. You, as the provider of this function, can provide whatever is needed for a parameter that the caller does not provide.



There are more detailed explanations of these problems found by searching for "flag parameters", for example. Martin Fowler Stack Overflow , they tend to mention boolean arguments, but it's essentially the same thing - a different code path depending on the parameter, which has no other effect.

Another phrase to look for is "control link"

+2


source


Here's another one that allows you to specify arguments and does not emit c=None

and c

not specified, but at the same time explicitly specifying the names of the arguments:

undefined = object()

def func(a, b, c=undefined, d=undefined):
    if (c is undefined) ^ (d is undefined):
        raise TypeError("Hey, provide exactly 1 of 'c' and 'd'")

    ...

      

In Python 3, keyword-only arguments make it even nicer by making sure the caller explicitly specifies c

either d

:

def func(a, b, *, c=undefined, d=undefined):
    if (c is undefined) ^ (d is undefined):
        raise TypeError("Hey, provide exactly 1 of 'c' and 'd'")

      

+1


source







All Articles