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]
.