How to assert that zero or only one of N given arguments is passed
I have a definition like this
def bar(self, foo=None, bar=None, baz=None):
pass
I want to make sure a maximum o开发者_运维技巧f one of foo, bar, baz is passed. I can do
if foo and bar:
raise Ex()
if foo and baz:
raise Ex()
....
But there got be something simpler.
How about:
initialisers = [foo, bar, baz]
if initialisers.count(None) < len(initialisers) - 1:
raise Ex()
It simply counts how many None
are present. If they're all None
or only one isn't then fine, otherwise it raises the exception.
x!=None
returns True
(whose numeric value is 1
!) for non-Nones, False
(whose numeric value is 0) for Nones. So,
sum(x!=None for x in (foo, bar, baz))
is the simplest way to count how many of those identifiers are bound to non-None values (and you can check that count against 1 just like other answers do for their ways of obtaining the count). This is a very general approach in that instead of x!=None
you could be using any strictly-bool predicate of interest; for example if you have a bunch of integers and want to know how many of them have 3
as the first digit of their decimal representation,
sum(str(abs(x)).startswith('3') for x in (a, b, c, d, e))
works fine too.
Don't be queasy about "summing bools": Python bools are sharply defined as a subclass of int with exactly two instances which have peculiar str/repr but otherwise behave exactly like the plain ints 0 and 1. There are good pragmatical reasons for this design and the ability to do arithmetic on bools is one of them, so feel free to use that ability!-)
Try
count = sum(map(lambda x: 0 if x is None else 1, (foo, bar, baz)))
if count > 1:
raise Ex()
That turns None
into 0 and everything into 1 and then sums everything up.
I have even shorter answer, with my favorite python feature - decorators:
def single_keyword(func):
def single_keyword_dec(*args, **kw):
if len(kw) > 1:
raise Exception("More than one initializer passed: {0}".format(kw.keys()))
return func(*args, **kw)
return single_keyword_dec
@single_keyword
def some_func(self, foo=None, bar=None, baz=None):
print foo, bar, baz
some_func(object, foo=0)
some_func(object, foo=0, bar=0)
#result
0 None None
Traceback (most recent call last):
File "dec2.py", line 13, in <module>
some_func(object, foo=0, bar=0)
File "dec2.py", line 4, in single_keyword_dec
raise Exception("More than one initializer passed: {0}".format(kw.keys()))
Exception: More than one initializer passed: ['foo', 'bar']
If you need distinguish between 'foo', 'bar', 'baz' and some other keywords, you could make similiar decorator which would accept list of keywords to restrict, and use it like this: @single_keyword('foo', 'bar', 'baz')
This way its 100% code reuse, no typing same thing over and over, and you get proper keywords in your function, not some obscure dict.
if len(filter(lambda x: x != None, locals().values())) > 1:
raise Exception()
Edited to address Alex's point.
Like this.
def func( self, **kw ):
assert len(kw) == 1, "Too Many Arguments"
assert kw.keys[0] in ( 'foo', 'bar', 'baz' ), "Argument not foo, bar or baz"
精彩评论