开发者

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"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜