118

When a Python list is known to always contain a single item, is there a way to access it other than:

mylist[0]

You may ask, 'Why would you want to?'. Curiosity alone. There seems to be an alternative way to do everything in Python.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Pyderman
  • 14,809
  • 13
  • 61
  • 106
  • 5
    There may be alternatives, but there's usually only one *obvious* way to do it - and, in this case, you seem to have already found it. – ekhumoro Oct 16 '15 at 02:23
  • 1
    if it *always* contains a single item, then maybe a `list` is not the best data type? – David Zemens Oct 16 '15 at 02:24
  • 18
    @ekhumoro: I'm actually partial to the sequence unpacking method, because it verifies the assumption that the sequence only has one element. `mylist[0]` succeeds when you have at least one element, but doesn't complain if you actually had 30 elements. `singleitem, = mylist` verifies that you've got exactly one element, no more, no less. – ShadowRanger Oct 16 '15 at 02:27
  • 1
    @ShadowRanger. The question is explicitly about accessing the only element in a list which is already *known* to contain a single item. – ekhumoro Oct 16 '15 at 02:34
  • 11
    @ekhumoro: No disagreement. I just prefer to program defensively, so violations of requirements don't pass silently (sure it fails, but it fails loudly, which is much easier to identify and fix than subtle misbehaviors). If I had a nickel for every time some "known" behavior in production code turned out to be dead wrong... Well, I probably wouldn't be _rich_, but I'd be able to take the family out to a _really_ nice dinner. – ShadowRanger Oct 16 '15 at 02:38
  • @DavidZemens Surprisingly there could be reasonable use cases where you know that a list should always contain a single element. What comes to my mind: **a)** API which you cannot change, **b)** API planned to serve multiple items in the future, **c)** branch of the code where the list should always contain a single item – pabouk - Ukraine stay strong Sep 15 '21 at 07:45

3 Answers3

174

Raises exception if not exactly one item:

Sequence unpacking:

singleitem, = mylist
# Identical in behavior (byte code produced is the same),
# but arguably more readable since a lone trailing comma could be missed:
[singleitem] = mylist

Rampant insanity, unpack the input to the identity lambda function:

# The only even semi-reasonable way to retrieve a single item and raise an exception on
# failure for too many, not just too few, elements as an expression, rather than a
# statement, without resorting to defining/importing functions elsewhere to do the work
singleitem = (lambda x: x)(*mylist)

All others silently ignore spec violation, producing first or last item:

Explicit use of iterator protocol:

singleitem = next(iter(mylist))

Destructive pop:

singleitem = mylist.pop()

Negative index:

singleitem = mylist[-1]

Set via single iteration for (because the loop variable remains available with its last value when a loop terminates):

for singleitem in mylist: break

There are many others (combining or varying bits of the above, or otherwise relying on implicit iteration), but you get the idea.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 8
    The first one is unique in that it also raises if the list contains more than one element. – Neil G Oct 16 '15 at 09:40
  • 9
    The first can also be written as `[singleitem] = mylist`. – Carsten S Oct 16 '15 at 10:00
  • @NeilG: Sadly, it's not wholly unique; the "rampant insanity" version in the comments does that too (as does the unpack via list syntax, though that's actually identical to the first example behavior-wise). I feel a bit dirty now. – ShadowRanger Oct 16 '15 at 12:57
  • 1
    @CarstenS: Usually, I frown on using `[]` for sequence unpacking (`for [i, x] in enumerate(iterable):` drives me batty), but yes, in the case of unpacking a single value, the trailing comma can easily be missed, and using brackets is justifiable for readability reasons. I've added it to the answer. Thanks! – ShadowRanger Oct 16 '15 at 14:47
  • I agree that it is not very pythonic, so I do not use it much any more, either. Only the old Haskeller in me wants to use it. – Carsten S Oct 16 '15 at 14:50
  • You can also use `ast` and prevent having a list with one element in the first place. #yolo – Bharel Sep 23 '16 at 22:24
  • @Bharel: Huh? I don't see how the `ast` module is related here, and can't think what else you're referring to... It's not always possible to avoid a `list` if the API is limited. – ShadowRanger Sep 23 '16 at 22:29
  • In every node you encounter a list creation, you can prevent creating it in the first place. (hoping you won't wreck havoc). That way there will never be any single-element list. Who needs them anyway. – Bharel Sep 23 '16 at 22:34
  • You may also override builtins.list.__new__ and hope for good. – Bharel Sep 23 '16 at 22:37
  • 1
    @Bharel: Okay, so as long as we're agreed that's batshit insane, we're good. :-) – ShadowRanger Sep 23 '16 at 23:15
  • `(singleitem,) = mylist` works too, of course. Somehow I can parse that one better. – Nathan Jun 14 '19 at 15:15
  • @Nathan: Sure. The lone trailing comma is still relatively easy to miss though; if the code was written by an inexperienced programmer, and I was maintaining it, I'd be half inclined to read it as another manifestation of unnecessary parentheses disease (the same disease that leads to `return(retval)`, `if (x in y):`, etc.). The problem isn't parsing once you notice it, it's noticing it in the first place, and the square brackets (without a comma) are visible, and have no subtle details to miss. – ShadowRanger Aug 01 '19 at 00:41
  • I'll add `singleitem, *_ = mylist` (it's derived from `head, *tail = mylist`). – David Waterworth Nov 30 '20 at 08:17
  • 1
    @DavidWaterworth: That silences the exception you'd get for passing a sequence with >1 item by silently collecting all the other items into `_` (making an empty `list` there if it's in fact single element); it's otherwise identical to `singleitem, = mylist`. – ShadowRanger Nov 30 '20 at 11:46
  • True, the safest/most explicit method may be to use something like https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.one - numpy has item() which I use frequently. – David Waterworth Nov 30 '20 at 22:48
  • Very useful answer! Could you please just confirm (in the answer) that there is no solution in a form of an expression (not assignment) raising an exception if the sequence does not contain only just single item? ... not counting the tools not in the standard library like `more_itertools.one()`. – pabouk - Ukraine stay strong Sep 15 '21 at 07:34
  • 1
    @pabouk: There technically is (someone just deleted the comment out of this comment chain apparently), it's just an insane solution I came up with (when I reference the "rampant insanity" version, that's the one), which is why I didn't put it in the answer. You'd just do: `(lambda x: x)(*mylist)`; `lambda`s are function definitions as expressions, so you can define one and then pass your unpacked iterable to one in a single line, and it either dies with an exception (if the unpacking doesn't match the argument count precisely) or calls the function successfully and returns the argument. – ShadowRanger Sep 15 '21 at 09:57
  • Black formats to `(x,) = xs`. – Mateen Ulhaq Jan 20 '23 at 19:04
  • @MateenUlhaq: Eh. Black is *very* opinionated. `x, = xs` is probably a bad idea (in that it's easy to miss the comma), but `(x,) = xs` is ugly (still needs that comma, and it feels incomplete when nothing follows it). If Black just prefers `(x,) = xs` to `x, = xs`, that's fine, but if it prefers `(x,) = xs` over `[x] = xs`, I think it's going too far; the latter is prettier, functionally identical, and just an unambigious. – ShadowRanger Jan 20 '23 at 19:14
  • Unfortunately, black can't convert to `[x] = xs` since that is not an equivalent AST transformation and changes program behavior since it constructs a new list. – Mateen Ulhaq Jan 20 '23 at 19:19
  • @MateenUlhaq: The AST isn't the same, but it doesn't change program behavior at all; the two versions are *identical* per [the language spec](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements); "Assignment of an object to a target list, optionally enclosed in parentheses or square brackets, is recursively defined as follows." Aside from that fact that `[]` eliminates the need for a trailing comma when unpacking to a single target due to *looking* like a `list` syntactically, they're both defined to be equivalent (no `tuple` or `list` is created). – ShadowRanger Jan 20 '23 at 19:42
  • 1
    Once the AST is compiled, they end up completely identical (a `LOAD_*` to load the original iterable to the stack, an `UNPACK_SEQUENCE 1` to assert it has one element and replace the item on the stack with that single element, and a `STORE_*` to put it in `x`). Granted, compiled opcodes aren't the spec, but since the end result supports the rules from the spec, it really is guaranteed to not change program behavior. – ShadowRanger Jan 20 '23 at 19:44
24

I will add that the more_itertools library has a tool that returns one item from an iterable.

from more_itertools import one


iterable = ["foo"]
one(iterable)
# "foo"

In addition, more_itertools.one raises an error if the iterable is empty or has more than one item.

iterable = []
one(iterable)
# ValueError: not enough values to unpack (expected 1, got 0)

iterable = ["foo", "bar"]
one(iterable)
# ValueError: too many values to unpack (expected 1)

more_itertools is a third-party package > pip install more-itertools

pylang
  • 40,867
  • 14
  • 129
  • 121
  • Back when you wrote this answer, [`more_itertools.one` was exactly equivalent to my primary solution, aside from variable names](https://github.com/more-itertools/more-itertools/blob/3.2.0/more_itertools/more.py#L420) (a two-liner, `element, = iterable`, then `return element`). It's since gotten *much* more complex (allowing custom exceptions for too short or too long, and therefore unable to benefit from unpacking performing the work efficiently, with Python raising exceptions for you). It's kind of a shame; it's rare the distinction matters, yet the code got much slower to accommodate it. – ShadowRanger Jan 20 '23 at 19:31
  • In particular, the new code is slower by the greatest margin for the success case (which is presumably the common case); in 3.11.1, the original implementation takes around 41 ns on a one-tuple on my machine, while the new one takes 348 ns. It's slower in the failure cases too (particularly when the iterable is too long), but those hardly matter (and the relative loss is much smaller, taking only 25-100% longer, not ~9x). – ShadowRanger Jan 20 '23 at 19:35
0

(This is an adjusted repost of my answer to a similar question related to sets.)

One way is to use reduce with lambda x: x.

from functools import reduce

> reduce(lambda x: x, [3]})
3

> reduce(lambda x: x, [1, 2, 3])
TypeError: <lambda>() takes 1 positional argument but 2 were given

> reduce(lambda x: x, [])
TypeError: reduce() of empty sequence with no initial value

Benefits:

  • Fails for multiple and zero values
  • Doesn't change the original list
  • Doesn't need a new variable and can be passed as an argument

Cons: "API misuse" (see comments).

nocibambi
  • 2,065
  • 1
  • 16
  • 22
  • This is stacking a lot of random garbage to get a result through, essentially, API misuse. It isn't gaining *any* benefit unique to the `lambda`, you could literally pass *any* function accepting *any* number of arguments aside from two (e.g. `ord`, `lambda: None`, etc.), and get the same result. It "works" because the function is never even called when there is only one thing in the input, and if it's called, it dies immediately. You may as well simplify it by just letting function call syntax do the job, as in my rampant insanity version (`(lambda x: x)(*mylist)`), with the same benefits. – ShadowRanger May 01 '22 at 22:02
  • 1
    To be clear, I enjoy stupid new ways to do simple things. I just don't support a claim that importing a function, and intentionally misusing it to get a benefit by side-effect from the misuse, is "Arguably less awkward and PEP-compliant" than literally any other solution out there. Also, just to be clear, this solution *does* involve transforming to an iterable; you don't see a call to `iter` because the language did it implicitly for you (the same way it does for unpacking, use of `for` loops, etc.). – ShadowRanger May 01 '22 at 22:06
  • @ShadowRanger Thanks for the feedback. I adjusted the post base on them. – nocibambi May 02 '22 at 13:37