views:

143

answers:

6

I have a definition like this

def bar(self, foo=None, bar=None, baz=None):
    pass

I want to make sure a maximum of 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.

+4  A: 

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.

Aaron Digulla
Thank you. Your solution works, I just found Scott's answer more explicit and simple so accpted it.
uswaretech
or without lambda:count = sum(0 if x == None else 1 for x in [foo, bar, baz])
JonahSan
Yeah, I'm not used to the new list thingy :)
Aaron Digulla
Using the count method is probably simplest, but if you're going to use a custom generator expression, probably the cleanest would be:count = sum(1 for x in [foo, bar, baz] if x is not None)
Jeffrey Harris
+9  A: 

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.

Scott Griffiths
Thanks. Short, sweet and simple. Me like.
uswaretech
Glad you like it. I had the same problem recently but with 15 initialisers.
Scott Griffiths
A: 

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"
S.Lott
Most elegant solution.Little error though : assert kw.keys0] in ...
peufeu
@S.Lott: -1 Yuk. Most **inelegant** solution. (1) `assert` will vanish if run with optimising flag (2) already need to do own arg-name checking (3) you have omitted the subsequent code for doing own default-handling e.g. `foo = kw.get('foo')`
John Machin
A: 
if len(filter(lambda x: x != None, locals().values())) > 1:
    raise Exception()

Edited to address Alex's point.

Brent Newey
This is wrong as it filters away 0s and ''s as well as Nones.
Alex Martelli
Fair enough. Fixed.
Brent Newey
+6  A: 

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!-)

Alex Martelli
+2  A: 

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.

Daniel Kluev