views:

487

answers:

7

I created a utility function to return the expected single item from an generator expression

print one(name for name in ('bob','fred') if name=='bob')

Is this a good way to go about it?

def one(g):
    try:
        val = g.next()
        try:
            g.next()
        except StopIteration:
            return val
        else:
            raise Exception('Too many values')
    except StopIteration:
        raise Exception('No values')
+1  A: 

Simple approach:

print (name for name in ('bob', 'fred') if name == 'bob').next()

If you really want an error when there is more than one value, then you need a function. The most simple I can think of is (EDITED to work with lists too):

def one(iterable):
    it = iter(iterable)
    val = it.next()
    try:
        it.next()
    except StopIteration:
        return val
    else:
        raise Exception('More than one value')
nosklo
+1  A: 

Have a look into the itertools.islice() method.

>>> i2=itertools.islice((name for name in ('bob','fred') if name=='bob'),0,1,1)
>>> i2.next()
'bob'
>>> i2.next()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
StopIteration
>>>

This module implements a number of iterator building blocks inspired by constructs from the Haskell and SML programming languages. Each has been recast in a form suitable for Python.

The module standardizes a core set of fast, memory efficient tools that are useful by themselves or in combination. Standardization helps avoid the readability and reliability problems which arise when many different individuals create their own slightly varying implementations, each with their own quirks and naming conventions.

The tools are designed to combine readily with one another. This makes it easy to construct more specialized tools succinctly and efficiently in pure Python.

gimel
this misses the point of the question
hop
+4  A: 

Do you mean?

def one( someGenerator ):
    if len(list(someGenerator)) != 1: raise Exception( "Not a Singleton" )

What are you trying to accomplish with all the extra code?

S.Lott
TypeError: object of type 'generator' has no len(). I guess I could : len(list(someGenerator))
GHZ
that's what I would do, it seems much clearer and should have minimal performance impact.
llimllib
since the generator is not expected to be unlimited, this seems to be the most elegant solution to this unelegant idea.
hop
+1  A: 

Here is my try at the one() function. I would avoid the explicit .next() call and use a for loop instead.

def one(seq):
    counter = 0
    for elem in seq:
        result = elem
        counter += 1
        if counter > 1:
            break
    if counter == 0:
        raise Exception('No values')
    elif counter > 1:
        raise Exception('Too many values')
    return result
unbeknown
This is better, as it works for lists too.
GHZ
-1: syntax error.
nosklo
Replaced the = with ==.
unbeknown
+1  A: 

First, (to answer the actual question!) your solution will work fine as will the other variants proposed.

I would add that in this case, IMO, generators are overly complicated. If you expect to have one value, you'll probably never have enough for memory usage to be a concern, so I would have just used the obvious and much clearer:

children = [name for name in ('bob','fred') if name=='bob']
if len(children) == 0:
    raise Exception('No values')
elif len(children) > 1:
    raise Exception('Too many values')
else:
    child = children[0]
dF
+5  A: 

A simpler solution is to use tuple unpacking. This will already do everything you want, including checking that it contains exactly one item.

Single item:

 >>> name, = (name for name in ('bob','fred') if name=='bob')
 >>> name
 'bob'

Too many items:

>>> name, = (name for name in ('bob','bob') if name=='bob')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

No items:

>>> name, = (name for name in ('fred','joe') if name=='bob')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 0 values to unpack
Brian
clever, but maybe too clever.
hop
A: 

How about using Python's for .. in syntax with a counter? Similar to unbeknown's answer.

def one(items):
    count = 0
    value = None

    for item in items:
        if count:
            raise Exception('Too many values')

        count += 1
        value = item

    if not count:
        raise Exception('No values')

    return value
jgeewax