Is there a way to force mutually exclusive function parameters in python?

python argparse
how many arguments can a function have in python
python command line arguments
python function default argument
python functions
python function argument type check
python function arguments
python function arguments list

Consider:

def foobar(*, foo, bar):
    if foo:
        print('foo', end="")
    if bar:
        print('bar', end="")
    if foo and bar:
        print('No bueno', end='')  # I want this to be impossible
    if not foo and not bar:
        print('No bueno', end='')  # I want this to be impossible
    print('')


foobar(foo='bar')  # I want to pass inspection
foobar(bar='foo')  # I want to pass inspection
foobar(foo='bar', bar='foo')  # I want to fail inspection
foobar()  # I want to fail inspection

Is there a way to set up a function so that way calling it only passes inspection when just one of foo or bar is being passed, without manually checking inside the function?

Syntactically no. However it's relatively easy to do this using a decorator:

from functools import wraps

def mutually_exclusive(keyword, *keywords):
    keywords = (keyword,)+keywords
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            if sum(k in keywords for k in kwargs) != 1:
                raise TypeError('You must specify exactly one of {}'.format(', '.join(keywords)))
            return func(*args, **kwargs)
        return inner
    return wrapper

Used as:

>>> @mutually_exclusive('foo', 'bar')
... def foobar(*, foo=None, bar=None):
...     print(foo, bar)
... 
>>> foobar(foo=1)
1 None
>>> foobar(bar=1)
None 1
>>> foobar(bar=1, foo=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar
>>> foobar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar

The decorator ignores positionals and keyword arguments not included in the list given:

>>> @mutually_exclusive('foo', 'bar')
... def foobar(a,b,c, *, foo=None, bar=None, taz=None):
...     print(a,b,c,foo,bar,taz)
... 
>>> foobar(1,2,3, foo=4, taz=5)
1 2 3 4 None 5
>>> foobar(1,2,3, foo=4, bar=5,taz=6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar

If the arguments might be "optional" (i.e. you may specify at most one of those keyword arguments, but you may also omit all of them) just change != 1 to <= 1 or in (0,1) as you prefer.

If you replace 1 with a number k you generalize the decorator to accept exactly (or at most) k of the specified arguments from the set you provided.

This however will not help PyCharm in anyway. As far as I know currently it's simply impossible to tell an IDE what you want.


The above decorator has a little "bug": it considers foo=None as if you passed a value for foo since it appears in the kwargs list. Usually you'd expect that passing the default value should behave identically as if you did not specify the argument at all.

Fixing this properly would require to inspect func inside wrapper to lookup the defaults and change k in keywords with something like k in keywords and kwargs[k] != defaults[k].

PEP 612 -- Parameter Specification Variables, There currently are two ways to specify the type of a callable, the of the Python idiom of one function passing all arguments given to it over to another function. expects single types, so they are currently mutually exclusive. If you want two or more arguments to be mutually exclusive. You can use the function argparse.ArgumentParser.add_mutually_exclusive_group(). In the example below, either foo or bar can exist but not both at the same time.

In short: no you cannot do that.

The closest you can get to that might be the use of an assertion:

def foobar(foo=None, bar=None):
    assert bool(foo) != bool(bar)

foobar(foo='bar')             # Passes
foobar(bar='foo')             # Passes
foobar(foo='bar', bar='foo')  # Raises an AssertionError
foobar()                      # Raises an AssertionError

The combination of the bool conversions and the != will make a logical XOR.

Be careful with assertions though; they can be disabled. It's fine if your check is required during development only.

argparse, Assuming the Python code above is saved into a file called prog.py , it can be run at the Generally, these calls tell the ArgumentParser how to take the strings on the ArgumentParser parses arguments through the parse_args() method. the arguments in the mutually exclusive group was present on the command line:. The mutually exclusive group is in fact a multiple option group. I just removed the other options for the sake of the post. Is it possible to use this default=do_work option for all mutually exclusive items and go there if there's no mutually exclusive parameter passed ? \$\endgroup\$ – AK47 Jun 2 '16 at 11:51

The standard library uses a simple runtime check for this:

def foobar(*, foo=None, bar=None):
    if (foo is None) == (bar is None):
        raise ValueError('Exactly one of `foo` and `bar` must be provided')

Programming FAQ, What does the slash(/) in the parameter list of a function mean? Numbers and strings How can I have modules that mutually import each other? __import__('​x.y.z') Abstractions tend to create indirections and force the interpreter to work more. If the levels of in the first place. These solutions are not mutually exclusive. In this function, if user does not give the second parameter b, it assumes it to be 10, but providing the first parameter is necessary. 5. Python Function Unknown Number of Parameters. NOTE: If there are, say 4 parameters in a function and a default value is defined for the 2nd one, then 3rd and 4th parameter should also be assigned a default

You could refactor slightly and take two non-optional parameters that together provide one value:

def foobar(name, value):
    if name == 'foo':
        foo = value
    elif name == 'bar':
        bar = value
    else:
        raise ValueError()

That way it's impossible to pass two foo or bar values. PyCharm would also warn you if you added extra parameters.

Ability to create mutually exclusive option groups. · Issue #257 , The programation of the callback functions can become very that features so many mutually exclusive parameters with different types? Enforce CLI option dependencies in a user-friendly way nchammas/flintrock#5. Python also supports named parameters, so that when a function is called, parameters can be explicitly assigned a value by name. These are often used to implement default, or optional, values. These are often used to implement default, or optional, values.

The Easy Guide to Python Command Line Arguments, One of the strengths of Python is that it comes with the ability to do just about anything. Notice how we used the .add_argument() function to pass “a” as a When set to True , this forces the user to have an input for that value, else of parser command line arguments which are mutually exclusive — i.e  Keyword arguments are related to the function calls. When you use keyword arguments in a function call, the caller identifies the arguments by the parameter name. This allows you to skip arguments or place them out of order because the Python interpreter is able to use the keywords provided to match the values with parameters.

How to Build Command Line Interfaces in Python With argparse , When to Use a Command Line Interface; How to Use the Python argparse Library Argument Does; Defining Mutually Exclusive Groups; Setting the Argument Name the parse_args() method args = my_parser.parse_args() input_path = args. and you want to force your users to specify the full name of the options they  Variable Function Arguments. Up until now, functions had a fixed number of arguments. In Python, there are other ways to define a function that can take variable number of arguments. Three different forms of this type are described below. Python Default Arguments. Function arguments can have default values in Python.

Modular Programming Languages: Joint Modular Languages Conference, , Although functions are first class values in Python, nested functions do not have However, closures can be simulated by specifying values as default arguments​. that wraps all the methods of an object to run in mutually exclusive mode. forces us to define a subclass that overrides each method to include the same  Concepts¶. Let’s show the sort of functionality that we are going to explore in this introductory tutorial by making use of the ls command: $ ls cpython devguide prog.py pypy rm-unused-function.patch $ ls pypy ctypes_configure demo dotviewer include lib_pypy lib-python $ ls -l total 20 drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython drwxr-xr-x 4 wena wena 4096 Feb 8 12:04 devguide

Comments
  • foo and bar are keyword-only parameters in that foobar definition, not optional parameters. (I've seen a lot of people make the opposite mistake, but this is the first time I've seen anyone mix things up in this direction.)
  • Yes I'm aware that they are not optional right now. I was wondering if there is a way to make it so that way to pass inspection when calling foobar, you could only pass one of foo or bar.
  • Make them both named with a setting to none and check that?
  • While that would work on making it fail, it still passes inspection, and so someone wouldn't realise something is wrong until running the code. This is where I'm coming from.
  • What do you mean by "it still passes inspection"? What sort of inspection do you want to catch this? Some specific automated tool? Code review by a programmer of a certain Python experience level?
  • This is a very pythonic way of achieving the desired results. Though I would suggest ValueError or RuntimeErrorin place of TypeError. Personal preference, I suppose.
  • @PMende I chose TypeError because typically the errors raised when you provide the wrong arguments raise TypeError (e.g. def f(a): pass then f(b=1) raises TypeError: f() got an unexpected keyword argument 'b'. So I believe TypeError is more consistent in this case since we are simulating a special signature for the function... But then this is an opinion.
  • Would this also work for a constructor of a class? Think of the canonical example of a circle class for which objects would be generated using a midpoint and either (exclusively) a radius or a diameter....
  • And for a python dataclass ?
  • @GertVdE You can manually define the __init__ and put the decorator on it (given that you work with mutually excusive arguments chances are you might want some other custom logic anyway). If you don't want to write your own custom __init__ chances are you have to use metaclasses. You should lookup some tutorial on python3 metaclasses.
  • Be careful checking for boolean equality instead of is None. If only one falsey parameter was passed this would still raise an exception.