在Python中从只含一个元素的列表中获取该元素?

121

当我们已知一个Python列表始终只包含一个元素时,有没有一种方法可以访问它而不是:

mylist[0]

您可能会问:为什么要这样做?

只是出于好奇。在Python中似乎有一种替代的方法来完成所有事情


5
可能有其他选择,但通常只有一种显而易见的方法 - 在这种情况下,你似乎已经找到了它。 - ekhumoro
1
如果它总是包含单一项,那么也许列表不是最合适的数据类型? - David Zemens
19
我更喜欢使用序列解包的方法,因为它可以验证序列只有一个元素的假设。mylist[0] 在至少有一个元素时会成功,但是如果你实际上有30个元素,它不会报错。singleitem, = mylist 可以验证你确切地只有一个元素,不多也不少。 - ShadowRanger
1
@ShadowRanger。这个问题明确是关于访问一个已知只包含单个元素的列表中唯一元素的问题。 - ekhumoro
11
没有异议。我只是更喜欢以防御性编程,这样违反要求的情况就不会悄无声息地通过(当然它会失败,但它会大声失败,比微妙的错误行为更容易识别和修复)。如果我有一便士,针对生产代码中某些“已知”行为实际上是完全错误的每次出现的话……好吧,也许我不会变得非常富裕,但我可以带上家人去享用一顿真正美味的晚餐。 - ShadowRanger
显示剩余2条评论
3个回答

177

如果不是恰好一个项,则引发异常:

序列解包:

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

狂乱无度,拆解输入到身份lambda函数:

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

其他所有的操作都会默默忽略规范违规,生成第一个或最后一个项:

显式使用迭代器协议:

singleitem = next(iter(mylist))

破坏性弹出:

singleitem = mylist.pop()

负索引:

singleitem = mylist[-1]

通过单次迭代 for(因为循环变量在循环终止时保留其最后一个值)进行设置:

for singleitem in mylist: break

还有许多其他的方法(结合或变化上述方法的位,或者以隐式迭代方式实现),但您已经掌握了要点。


8
第一个独特的地方在于,当列表包含多个元素时,它也会增加。 - Neil G
9
第一种写法也可以表示为 [singleitem] = mylist - Carsten S
1
通常情况下,我不赞成使用[]进行序列解包(例如for [i, x] in enumerate(iterable):会让我发疯),但是在解包单个值的情况下,尾随逗号很容易被忽略,出于可读性的原因使用方括号是可以接受的。我已经将其添加到答案中。谢谢! - ShadowRanger
1
@pabouk:技术上有(显然有人刚刚删除了这个评论链中的评论),但那只是我想出来的一个疯狂的解决方案(当我提到“疯狂的狂热”版本时,就是那个版本),这就是为什么我没有把它放在答案中的原因。你只需要做:(lambda x: x)(*mylist)lambda是函数定义作为表达式,所以你可以定义一个函数,然后将你的可拆包迭代对象传递给其中一个单行函数,它要么因为拆包不精确而死亡(如果拆包不匹配参数计数),要么成功调用函数并返回参数。 - ShadowRanger
1
一旦AST被编译,它们最终完全相同(一个LOAD_*将原始可迭代对象加载到堆栈中,一个UNPACK_SEQUENCE 1来断言它只有一个元素并用该单个元素替换堆栈上的项,以及一个STORE_*将其放入x中)。当然,编译后的操作码不是规范,但由于最终结果支持规范中的规则,因此确保不会改变程序行为。 - ShadowRanger
显示剩余17条评论

24

我要补充说,more_itertools库中有一个工具,可以从可迭代对象中返回一个项目。

from more_itertools import one


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

此外,more_itertools.one会在可迭代对象为空或多于一个项时引发错误。

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 是一个第三方包 > pip install more-itertools


当你写这个答案的时候,more_itertools.one与我的主要解决方案完全等效(一个两行代码的解决方案,element, = iterable,然后 return element)。现在它变得更加复杂了(允许自定义异常,对于太短或太长的情况,因此无法从拆包中获得高效的工作,Python会为您引发异常)。这有点可惜;很少有区别是重要的,但代码变得更慢以适应它。 - ShadowRanger
特别是,新代码在成功情况下的速度差距最大(这可能是常见情况);在3.11.1中,原始实现在我的机器上对一个元组需要约41 ns,而新实现需要348 ns。在失败情况下它也更慢(特别是当可迭代对象太长时),但这些几乎不重要(相对损失要小得多,只需要比原来多花费25-100%的时间,而不是 ~9倍)。 - ShadowRanger

0

(这是对与集合相关的类似问题的我的回答进行调整后的重新发布。)

一种方法是使用reducelambda 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

优点:

  • 支持多个和零值的失败处理
  • 不改变原始列表
  • 不需要新的变量,可作为参数传递

缺点:可能存在“API误用”(请参阅注释)。


这是通过堆叠大量随机垃圾来获得结果的,本质上是 API 滥用。它没有获得与 lambda 独特的任何好处,你可以传递 任何 接受除两个参数以外的 任何 数量的函数(例如 ordlambda: None 等),并获得相同的结果。它“工作”的原因是当输入中只有一个元素时,函数甚至不会被调用,如果被调用,它会立即死亡。你可以简化它,让函数调用语法完成工作,就像我疯狂的版本((lambda x: x)(*mylist))一样,具有相同的好处。 - ShadowRanger
1
要明确的是,我喜欢愚蠢的新方法来完成简单的事情。我只是不支持一个声称导入函数,并有意地误用它通过副作用从误用中获益,“可以认为比任何其他解决方案都更不尴尬和符合PEP”的说法。此外,为了明确起见,这个解决方案确实涉及转换为可迭代对象;你看不到对 iter 的调用,因为语言隐式地为你执行了它(就像拆包、使用 for 循环等一样)。 - ShadowRanger
@ShadowRanger 感谢您的反馈。我根据它们进行了调整。 - nocibambi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接