# How to get an item from a simple set?

Discussion in 'Python' started by Pete Forman, Nov 24, 2004.

1. ### Pete FormanGuest

I have a set that contains one item. What is the best way of getting
at that item? Using pop() empties the set. Here is what I've tried.

Python 2.3.4 (#1, Jun 13 2004, 11:21:03)
[GCC 3.3.1 (cygming special)] on cygwin
>>> from sets import Set
>>> s = Set(['foo'])
>>> s.copy().pop()

'foo'
>>> [x for x in s][0]

'foo'
>>> s[0]

Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unindexable object

--
Pete Forman -./\.- Disclaimer: This post is originated
WesternGeco -./\.- by myself and does not represent
-./\.- opinion of Schlumberger, Baker
http://petef.port5.com -./\.- Hughes or their divisions.

Pete Forman, Nov 24, 2004

2. ### Steven BethardGuest

Pete Forman wrote:
> I have a set that contains one item. What is the best way of getting
> at that item? Using pop() empties the set. Here is what I've tried.

This is what tuple unpacking is for:

>>> s = set(['foo'])
>>> item, = s
>>> item

'foo'
>>> [item] = s
>>> item

'foo'

It's up to you whether you like the tuple or list syntax better. =)

Steve

Steven Bethard, Nov 24, 2004

3. ### Skip MontanaroGuest

Pete> I have a set that contains one item. What is the best way of
Pete> getting at that item? Using pop() empties the set.

Do you want to enumerate all the items in the set? If so:

for elt in s:
print elt

If you just want to grab one arbitrary (though not random) item from the
set, try:

elt = iter(s).next()

Note that repeating this operation will always return the same item:

>>> s

set(['jkl', 'foo', 'abc', 'def', 'ghi'])
>>> iter(s).next()

'jkl'
>>> iter(s).next()

'jkl'
>>> iter(s).next()

'jkl'
>>> iter(s).next()

'jkl'

Skip

Skip Montanaro, Nov 24, 2004
4. ### Pete FormanGuest

Skip Montanaro <> writes:

> Pete> I have a set that contains one item. What is the best way of
> Pete> getting at that item? Using pop() empties the set.
>
> If you just want to grab one arbitrary (though not random) item from the
> set, try:
>
> elt = iter(s).next()

I actually wanted to append the single item to a string, Steven's
solutions work for assignment.

So this looks like my best bet. I'll probably use join instead of +=
in my code.

>>> line = 'bar '
>>> line += iter(s).next()
>>> line

'bar foo'

--
Pete Forman -./\.- Disclaimer: This post is originated
WesternGeco -./\.- by myself and does not represent
-./\.- opinion of Schlumberger, Baker
http://petef.port5.com -./\.- Hughes or their divisions.

Pete Forman, Nov 24, 2004
5. ### Steven BethardGuest

Pete Forman wrote:
> I actually wanted to append the single item to a string, Steven's
> solutions work for assignment.

[snip]
>>>>line = 'bar '
>>>>line += iter(s).next()
>>>>line

> 'bar foo'

Yeah, using the assignment's an extra line:

>>> line_list = ['bar ']
>>> item, = s
>>> line_list.append(item)
>>> ''.join(line_list)

'bar foo'

I still tend to write the extra line in cases like this -- it guarantees
that the set is really the size that I think it is, where the
iter(s).next() solution will not raise an exception if the set is
actually larger.

Steve

Steven Bethard, Nov 24, 2004
6. ### Pete FormanGuest

Steven Bethard <> writes:

> I still tend to write the extra line in cases like this -- it
> guarantees that the set is really the size that I think it is, where
> the iter(s).next() solution will not raise an exception if the set
> is actually larger.

The preceding line in my code is
if len(s) == 1:

--
Pete Forman -./\.- Disclaimer: This post is originated
WesternGeco -./\.- by myself and does not represent
-./\.- opinion of Schlumberger, Baker
http://petef.port5.com -./\.- Hughes or their divisions.

Pete Forman, Nov 24, 2004
7. ### Bengt RichterGuest

On Wed, 24 Nov 2004 09:46:50 -0600, Skip Montanaro <> wrote:

>
> Pete> I have a set that contains one item. What is the best way of
> Pete> getting at that item? Using pop() empties the set.
>
>Do you want to enumerate all the items in the set? If so:
>
> for elt in s:
> print elt
>
>If you just want to grab one arbitrary (though not random) item from the
>set, try:
>
> elt = iter(s).next()
>
>Note that repeating this operation will always return the same item:
>
> >>> s

> set(['jkl', 'foo', 'abc', 'def', 'ghi'])
> >>> iter(s).next()

> 'jkl'
> >>> iter(s).next()

> 'jkl'
> >>> iter(s).next()

> 'jkl'
> >>> iter(s).next()

> 'jkl'
>

Lest someone else not realize that the operation you are repeating includes creating
a fresh initialized iterator each time, and you're just doing it as a way to grab one element:

>>> s = set(['jkl', 'foo', 'abc', 'def', 'ghi'])
>>> it = iter(s)
>>> it.next()

'jkl'
>>> it.next()

'foo'
>>> it.next()

'abc'
>>> it.next()

'def'
>>> it.next()

'ghi'
>>> it.next()

Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration

Regards,
Bengt Richter

Bengt Richter, Nov 24, 2004
8. ### Bengt RichterGuest

On Wed, 24 Nov 2004 15:40:30 GMT, Steven Bethard <> wrote:

>Pete Forman wrote:
>> I have a set that contains one item. What is the best way of getting
>> at that item? Using pop() empties the set. Here is what I've tried.

>
>This is what tuple unpacking is for:
>
> >>> s = set(['foo'])
> >>> item, = s
> >>> item

>'foo'
> >>> [item] = s
> >>> item

>'foo'
>
>It's up to you whether you like the tuple or list syntax better. =)
>

Thanks. I didn't realize a list format could be used to specify target names
like that. My intial reaction is a bendy feeling in my list expression syntax
recognizer, though. I'm not sure I like that on the left hand side.
It feels too much like __setitem__ on some implied object. The tuple syntax
on the left hand side is only for unpacking (unless you want to imagine invoking
an implied unnamed function, but that's a stretch IMO), so it doesn't trigger
that near-miss syntax recognition feeling.

Regards,
Bengt Richter

Bengt Richter, Nov 24, 2004
9. ### Steven BethardGuest

Pete Forman wrote:
> Steven Bethard <> writes:
>
>
>>I still tend to write the extra line in cases like this -- it
>>guarantees that the set is really the size that I think it is, where
>>the iter(s).next() solution will not raise an exception if the set
>>is actually larger.

>
>
> The preceding line in my code is
> if len(s) == 1:
>

So is this just one branch of a case statement? What do you do in the
case that len(s) != 1? And which one happens more often?

If I have two possible unpackings of an iterable and I know one is much
more common than the other, I often do something like:

try:
x, y = s # more common unpacking
except ValueError:
[x], y = s, None # less common ('exceptional') unpacking

This is a reasonable pattern if your code really does favor one branch
substantially over the other. But dont' take my word for it. Here's
what timeit says:

----- test.py ----
def test_cond(*args):
if len(args) == 1:
[x], y = args, None
elif len(args) == 2:
x, y = args
else:
raise ValueError('wrong number of arguments')

def test_try(*args):
try:
x, y = args
except ValueError:
[x], y = args, None

def test(fn, single_times, double_times):
for _ in range(single_times):
fn(1)
for _ in range(double_times):
fn(0, 1)

---- command prompt ----
>python -m timeit -s "import test" "test.test(test.test_cond, 10, 10)"

10000 loops, best of 3: 26.7 usec per loop

>python -m timeit -s "import test" "test.test(test.test_try, 10, 10)"

10000 loops, best of 3: 116 usec per loop

>python -m timeit -s "import test" "test.test(test.test_cond, 1, 100)"

10000 loops, best of 3: 132 usec per loop

>python -m timeit -s "import test" "test.test(test.test_try, 1, 100)"

10000 loops, best of 3: 99.8 usec per loop

As you can see, when the try/except block is slower when the two
branches get traversed approximately equally, but faster when one branch
is substantially favored over the other.

Steve

Steven Bethard, Nov 24, 2004
10. ### Steven BethardGuest

Bengt Richter wrote:
> On Wed, 24 Nov 2004 15:40:30 GMT, Steven Bethard <> wrote:
>>
>>>>>[item] = s
>>>>>item

>>
>>'foo'
>>
>>It's up to you whether you like the tuple or list syntax better. =)
>>

>
> Thanks. I didn't realize a list format could be used to specify target names
> like that. My intial reaction is a bendy feeling in my list expression syntax
> recognizer, though. I'm not sure I like that on the left hand side.
> It feels too much like __setitem__ on some implied object. The tuple syntax
> on the left hand side is only for unpacking (unless you want to imagine invoking
> an implied unnamed function, but that's a stretch IMO), so it doesn't trigger
> that near-miss syntax recognition feeling.

Yeah, I almost always prefer the tuple (comma) syntax, but occasionally
I find the list syntax clearer, if, for example, I'm unpacking a nested
single-item list:

>>> t

[['abcd'], 1, 2]
>>> (x,), y, z = t
>>> x, y, z

('abcd', 1, 2)

The ,), in the tuple-only unpacking makes me uncomfortable for some
reason. I feel marginally more comfortable with:

>>> [x], y, z = t
>>> x, y, z

('abcd', 1, 2)

Of course, I generally feel uncomfortable if I have a weird unpacking
thing like this anyway. It pretty much only comes up for me when I want
to assign some default values in one branch of a try/except or if/else
statement, e.g.

try:
x, y = s
except ValueError:
[x], y = s, None

Steve

Steven Bethard, Nov 24, 2004
11. ### Pete FormanGuest

Steven Bethard <> writes:

> So is this just one branch of a case statement? What do you do in
> the case that len(s) != 1? And which one happens more often?

My s contains a set of possible values that are being refined. I'm
printing either a single resolved value or "many" to indicate there is
still some way to go. len(s) > 1 is not exceptional though len(s) ==
0 would be. Over the run of the program one and many will happen
equally often. Performance is less of an issue than clarity of the
code.
--
Pete Forman -./\.- Disclaimer: This post is originated
WesternGeco -./\.- by myself and does not represent
-./\.- opinion of Schlumberger, Baker
http://petef.port5.com -./\.- Hughes or their divisions.

Pete Forman, Nov 26, 2004