为什么列表没有像字典一样安全的“获取”方法?


264

为什么列表没有像字典一样安全的“获取”方法?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range

1
列表的用途不同于字典。对于典型的list用例,不需要get()。但是,对于字典,get()通常很有用。
mgronber

42
如果您要求使用分片,则总是可以从列表中得到一个空的子列表,而无需提高IndexError:例如,而l[10:11]不是l[10]。()如果子列表存在,则子列表将具有所需的元素)
jsbueno

56
与这里的一些人相反,我支持安全的想法.get。它相当于l[i] if i < len(l) else default,但更易读,更简洁,并且i可以成为表达式而不必重新计算它
Paul Draper 2013年

6
今天,我希望这个存在。我使用了昂贵的函数来返回列表,但是我只想要第一个项目,或者None如果不存在的话。这本来很好,x = expensive().get(0, None)所以我不必将无用的昂贵返回值放入一个临时变量中。
Ryan Hiebert 2014年

2
@Ryan我的回答可能会帮助您stackoverflow.com/a/23003811/246265
Jake

Answers:


112

最终,它可能没有一个安全的.get方法,因为a dict是一个关联集合(值与名称相关联),在这种情况下,检查键是否存在(并返回其值)而不抛出异常是非常低效的,而这是非常琐碎的避免异常访问列表元素(因为该len方法非常快)。该.get方法允许您查询与名称关联的值,而不直接访问字典中的第37个项目(这更像是您要查询的列表中的内容)。

当然,您可以自己轻松实现此目的:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

您甚至可以在中将其猴子修补到__builtins__.list构造函数上__main__,但是由于大多数代码不使用它,因此更改的普及程度较小。如果您只想将此代码与自己的代码创建的列表一起使用,则可以简单地子类化list并添加get方法。


24
Python不允许使用Monkeypatching内置类型,例如list
Imran

7
@CSZ:.get解决了列表没有的问题-一种有效的方法,可以避免在获取可能不存在的数据时出现异常。知道什么是有效的列表索引是非常简单且非常有效的,但是对于字典中的键值没有特别好的方法。
尼克·巴斯汀,

10
我认为这根本不是关于效率的问题-检查字典中是否存在键和/或返回项是否存在O(1)。从原始角度来看,它不会像检查一样快len,但是从复杂性的角度来看,它们都是如此O(1)。正确的答案是典型的用法/语义...
马克·隆air11年

3
@Mark:并非所有O(1)都相等。而且,dict仅是最佳情况O(1),并非所有情况都如此。
尼克·巴斯汀,

4
我认为人们在这里没有抓住重点。讨论不应该是关于效率的。请停止进行过早的优化。如果您的程序太慢,则说明您正在滥用.get()或在代码(或环境)的其他地方遇到问题。使用这种方法的重点是代码可读性。“香草”技术在需要完成的每个位置都需要四行代码。该.get()技术仅需一项,并且可以轻松地与后续方法调用(例如my_list.get(2, '').uppercase())链接在一起。
泰勒·克伦普顿

66

如果您想要第一个元素,例如 my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

我知道这并非您真正要求的,但可能会帮助其他人。


7
比函数式编程少的pythonic
Eric

next(iter(my_list[index:index+1]), 'fail')允许任何索引,不仅是0。或者更少的FP,但是可以说是更多的Python语言,并且几乎可以肯定是更具可读性的:my_list[index] if index < len(my_list) else 'fail'
Alphabetasoup

47

可能是因为它对列表语义没有多大意义。但是,您可以通过子类化轻松创建自己的类。

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()

5
到目前为止,这是对OP的最pythonic回答。请注意,您还可以提取子列表,这在Python中是安全的操作。给定mylist = [1,2,3],您可以尝试使用mylist [8:9]提取第9个元素,而不会触发异常。然后,您可以测试列表是否为空,如果不为空,则从返回的列表中提取单个元素。
jose.angel.jimenez,2015年

1
这应该是公认的答案,而不是其他非Python的单线黑客,尤其是因为它保留了与字典的对称性。
埃里克

1
仅仅因为您需要一个不错的get方法就可以用子类化自己的列表就没有Pythonic了。可读性很重要。而且,每增加一个不必要的类,可读性都会受到影响。仅使用该try / except方法而不创建子类。
Jeyekomon

@Jeyekomon通过子类化来减少样板是完全Pythonic的。
基思(Keith)

42

代替使用.get,这样使用列表应该可以。只是用法上的差异。

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

15
如果我们尝试使用-1获取最新元素,则此操作将失败。
pretobomba 2015年

请注意,这不适用于循环链接的列表对象。另外,语法使我喜欢称之为“扫描块”。遍历代码以查看其功能时,这条线会使我慢一点。
泰勒·克朗普顿

内联if / else不适用于2.6之类的旧python(或者2.5?)
Eric

3
@TylerCrompton:python中没有循环链表。如果您自己编写一个,则可以自由地实现一个.get方法(除非我不确定在这种情况下您将如何解释索引的含义,否则它为什么会失败)。
尼克·巴斯汀

另一种处理越界负指数的方法是lst[i] if -len(lst) <= i < len(l) else 'fail'
mic

17

试试这个:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'

1
我喜欢这个,尽管它首先需要创建一个新的子列表。
瑞克(Rick)

15

jose.angel.jimenez


对于“单线”粉丝…


如果需要列表的第一个元素,或者如果列表为空,则需要默认值,请尝试:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

退货 a

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

退货 default


其他元素的示例...

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

默认回退...

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

经过测试 Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)


1
替代方案:value, = liste[:1] or ('default',)。看起来您需要括号。
qräbnö

13

最好的办法是将列表转换成字典,然后使用get方法访问它:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')

20
一个合理的解决方法,但是几乎不是“您可以做的最好的事情”。
2014年

3
但是效率很低。注意:除了那zip range len件事,您还可以使用dict(enumerate(my_list))
Marian 2015年

3
这不是最好的事情,而是您可以做的最坏的事情。
erikbwork

3
如果考虑性能,那是最糟糕的事情……如果您关心性能,那么您就不会使用像python这样的解释语言进行编码。我发现使用字典的解决方案相当优雅,功能强大且具有Python风格。无论如何,早期的优化都是邪恶的,因此让我们听一听,然后再看看这是一个瓶颈。
埃里克

7

因此,我对此进行了更多研究,结果发现没有任何针对此的东西。当我找到list.index(value)时,我感到很兴奋,它返回指定项目的索引,但是没有什么可用于获取特定索引处的值的。因此,如果您不想使用safe_list_get解决方案,我认为这是相当不错的。以下是一些liner if语句,这些语句可以根据情况为您完成工作:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

您也可以使用None代替'No',这更有意义。

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

另外,如果您只想获取列表中的第一项或最后一项,则可以使用

end_el = x[-1] if x else None

您也可以将它们变成函数,但我仍然喜欢IndexError异常解决方案。我尝试了该safe_list_get解决方案的简化版本,并使其变得更简单(没有默认设置):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

还没有进行基准测试以了解最快的方法。


1
不是真的pythonic。
埃里克

@Eric哪个代码段?我认为尝试,除了通过再次查看才最有意义。
radtek

独立功能不是pythonic。实际上,异常确实有点像pythonic,但由于它是编程语言中的一种常见模式,所以异常程度不高。更重要的是pythonic是一个新对象,该对象list通过子类化扩展了内置类型。这样,构造函数可以采用list或行为类似于列表的方式,而新实例的行为类似于list。请参阅下面的基思答案,它应该是公认的恕我直言。
艾瑞克(Eric)

1
@Eric我不是将问题解析为特定于OOP的问题,而是将其解析为“为什么列表没有类比以dict.get()从列表索引引用中返回默认值而不需要捕获IndexError? vs. FP context。)此外,您可能必须将您对“ pythonic”的使用限定为WWGD(因为他对FP Python的轻蔑是众所周知的),而不一定只是满足PEP8 /
20。– cowbert

1
el = x[4] if len(x) == 4 else 'No'–你的意思是len(x) > 4x[4]如果超出界限len(x) == 4
麦克风

4

字典用于查找。询问条目是否存在是很有意义的。列表通常是迭代的。通常不问L [10]是否存在,而是问L的长度是否为11。


是的,同意你的观点。但是我只是解析页面“ / group / Page_name”的相对URL。用“ /”分隔它,并想检查PageName是否等于某个页面。写类似[url.split('/')。get_from_index(2,None)==“ lalala”]的东西很舒服,而不是额外检查长度或捕获异常或编写自己的函数。可能您是对的,只是将其视为不寻常。无论如何,我还是不同意这个=)
CSZ

@Nick Bastin:没错。这全都与简单性和编码速度有关。
CSZ

如果要在键为连续整数的情况下使用列表作为节省空间的字典,这也将很有用。当然,负索引的存在已经阻止了这一点。
锑2012年

-1

您的用例基本上只与固定长度的数组和矩阵有关,这样您就可以知道它们有多长时间。在这种情况下,通常还需要在手动将它们填充为None或0之前创建它们,以便实际上您将使用的任何索引都已经存在。

你可以这样说:我经常在字典上需要.get()。作为一名全职程序员十年后,我认为我从不需要它了。:)


我的评论示例如何?还有什么更简单易读?(url.split('/')。getFromIndex(2)==“ lalala”)或(结果= url.split(); len(result)> 2并且result [2] ==“ lalala”)。是的,我知道我可以自己编写这样的函数=),但是我很惊讶这样的函数没有内置。
CSZ

1
同上说,您做错了。URL处理应该通过路由(模式匹配)或对象遍历来完成。但是,请回答您的具体情况:'lalala' in url.split('/')[2:]。但是,此处解决方案的问题在于,您仅关注第二个元素。如果URL是“ / monkeybonkey / lalala”怎么办?True即使网址无效,您也会得到一个提示。
Lennart Regebro 2011年

我只选了第二个要素,因为我只需要第二个要素。但是,是的,切片似乎是不错的替代选择
CSZ

@CSZ:但是,第一个元素将被忽略,在这种情况下,您可以跳过它。:)明白我的意思了,这个例子在现实生活中效果不佳。
Lennart Regebro 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.