【问题标题】:Best way to handle list.index(might-not-exist) in python?在python中处理list.index(可能不存在)的最佳方法?
【发布时间】:2011-01-09 02:47:50
【问题描述】:

我的代码看起来像这样:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

好的,这很简单,但你明白了。现在thing 可能实际上不在列表中,在这种情况下,我想将 -1 作为thing_index 传递。在其他语言中,这是您期望 index() 在找不到元素时返回的内容。事实上,它会抛出一个ValueError

我可以这样做:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

但这感觉很脏,而且我不知道ValueError 是否可以出于其他原因提出。我根据生成器函数想出了以下解决方案,但看起来有点复杂:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

有没有更简洁的方法来实现同样的目标?假设列表没有排序。

【问题讨论】:

  • "...在这种情况下,我想将 -1 作为thing_index 传递。" - 这绝对不是 Pythonic。在操作不成功的情况下传递(无意义的)令牌值是不受欢迎的 - 异常在这里确实是正确的方法。特别是因为thing_list[-1] 是一个有效的表达式,表示列表中的最后一个条目。
  • @jellybean: facepalm...发现 java 编码器:P
  • @Tim:有str.find 方法可以做到这一点:当在主题中找不到针时返回-1
  • @Tim None 会更好......这类似于 dict[key] vs dict.get[key]
  • @SilentGhost:嗯,很有趣。我可能需要更详细地研究这一点。如果没有找到搜索字符串,str.index() 会抛出异常。

标签: python list find indexing


【解决方案1】:

从 Python 3.6 开始,indexrindexfindrfind 方法,它们返回 -1 而不是抛出异常。

【讨论】:

    【解决方案2】:

    直言不讳:这里的答案非常糟糕,而且时间复杂度高得离谱。

    这是一个简单的方法。

    使用dict().get('key', 'some_value'),将返回'key'处的值,如果key不在字典中,则返回'some_value'

    您可以使用您的列表及其索引创建这样的字典。

    mylist = ['cat' 'dog', 'bunny']
    
    mapping = {value: index for index, value in enumerate(mylist)}
    

    然后,mapping.get('key', 0) 将返回索引(如果找到),或者None

    mapping.get('penguin', 0)  # returns 0
    

    【讨论】:

    • 我会直言不讳:: 你的代码不起作用。您需要将 [] 替换为 {} 以构建字典。
    【解决方案3】:

    如果您经常这样做,那么最好将其存放在辅助函数中:

    def index_of(val, in_list):
        try:
            return in_list.index(val)
        except ValueError:
            return -1 
    

    【讨论】:

    • 别忘了-1 是一个有效的索引:in_list[-1],虽然显然不是从index() 返回的。可能返回None
    【解决方案4】:

    已经有一段时间了,但它是标准库的核心部分,并且有许多潜在的方法,所以我认为为不同的建议提供一些基准并包括迄今为止最快的 numpy 方法是很有用的。

    import random
    from timeit import timeit
    import numpy as np
    
    l = [random.random() for i in range(10**4)]
    l[10**4 - 100] = 5
    
    # method 1
    def fun1(l:list, x:int, e = -1) -> int:
        return [[i for i,elem in enumerate(l) if elem == x] or [e]][0]
    
    # method 2
    def fun2(l:list, x:int, e = -1) -> int:
        for i,elem in enumerate(l):
            if elem == x:
                return i
        else:
            return e
    
    # method 3
    def fun3(l:list, x:int, e = -1) -> int:
        try:
            idx = l.index(x)
        except ValueError:
            idx = e
        return idx
    
    # method 4
    def fun4(l:list, x:int, e = -1) -> int:
        return l.index(x) if x in l else e
    
    l2 = np.array(l)
    # method 5
    def fun5(l:list or np.ndarray, x:int, e = -1) -> int:
        res = np.where(np.equal(l, x))
        if res[0].any():
            return res[0][0]
        else:        
            return e
    
    
    if __name__ == "__main__":
        print("Method 1:")
        print(timeit(stmt = "fun1(l, 5)", number = 1000, globals = globals()))
        print("")
        print("Method 2:")
        print(timeit(stmt = "fun2(l, 5)", number = 1000, globals = globals()))
        print("")
        print("Method 3:")
        print(timeit(stmt = "fun3(l, 5)", number = 1000, globals = globals()))
        print("")
        print("Method 4:")
        print(timeit(stmt = "fun4(l, 5)", number = 1000, globals = globals()))
        print("")
        print("Method 5, numpy given list:")
        print(timeit(stmt = "fun5(l, 5)", number = 1000, globals = globals()))
        print("")
        print("Method 6, numpy given np.ndarray:")
        print(timeit(stmt = "fun5(l2, 5)", number = 1000, globals = globals()))
        print("")
    

    当作为 main 运行时,这会在我的机器上显示以下打印输出,以秒为单位指示完成每个功能 1000 次试验的时间:

    方法一: 0.7502102799990098

    方法二: 0.7291318440002215

    方法三: 0.24142152300009911

    方法四: 0.5253471979995084

    方法5,numpy给定列表: 0.5045417560013448

    方法6,numpy给定np.ndarray: 0.011147511999297421

    当然,这个问题专门询问列表,所以最好的解决方案是使用 try-except 方法,但是通过使用 numpy 数据结构和运算符而不是python 数据结构很重要,如果在许多数据数组上构建对性能至关重要的东西,那么作者应该尝试在整个过程中使用 numpy 来利用超快的 C 绑定。 (CPython解释器,其他解释器性能可能有所不同)

    顺便说一句,方法 5 比方法 6 慢得多的原因是因为 numpy 首先必须将给定的列表转换为它自己的 numpy 数组,所以给它一个列表并不会破坏它,只是没有充分利用速度可能。

    【讨论】:

      【解决方案5】:

      实现比较

      Python 3.8 上的简单比较

      TL;DR maybeidx2 通常更快,但有很多未命中的数组 (n

      def maybeidx1(l, v):
          return l.index(v) if v in l else None
      
      def maybeidx2(l, v):
          try:
              return l.index(v)
          except ValueError:
              return None
      

      测试用例:

      a = [*range(100_000)]
      # Case 1: index in list
      maybeidx1(a, 50_000)
      Out[20]: 50000
      maybeidx2(a, 50_000)
      Out[21]: 50000
      # Case 2: index not in list
      maybeidx1(a, 100_000) is None
      Out[23]: True
      maybeidx2(a, 100_000) is None
      Out[24]: True
      

      时序案例 1

      %timeit maybeidx1(a, 50_000)
      1.06 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
      %timeit maybeidx2(a, 50_000)
      530 µs ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
      

      时序案例 2

      %timeit maybeidx1(a, 100_000)
      1.07 ms ± 21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
      %timeit maybeidx2(a, 100_000)
      1.07 ms ± 16.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
      

      结果

      对较大的数组使用maybeidx2 方法。这更快,因为maybeidx1 对数组进行了两次扫描以搜索值 - 这仍然是 O(n) 时间,但具有恒定的乘数 2,因此在实践中速度较慢。这适用于列表中存在值的情况。当该值不存在时,这些时间将大致相等;他们都必须准确地扫描整个数组一次,然后返回Nonetry-except 的开销可以忽略不计,即使数组大小为 10 - 除非 发生第二种情况。然后try-except 开销很明显。示例:

      a = [*range(10)]
      %timeit maybeidx1(a, 10)
      191 ns ± 2.61 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
      %timeit maybeidx2(a, 10)
      566 ns ± 5.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
      

      a 有超过 100 个元素时(在我的机器上)这个开销可以忽略不计。

      【讨论】:

        【解决方案6】:

        这个呢?:

        li = [1,2,3,4,5] # create list 
        
        li = dict(zip(li,range(len(li)))) # convert List To Dict 
        print( li ) # {1: 0, 2: 1, 3: 2, 4:3 , 5: 4}
        li.get(20) # None 
        li.get(1)  # 0 
        

        【讨论】:

        • 这正是我想要的:如果找不到元素,则获取默认值 - 为此编写函数或 if-else 似乎有点过头了。
        【解决方案7】:

        这样怎么样:

        temp_inx = (L + [x]).index(x) 
        inx = temp_inx if temp_inx < len(L) else -1
        

        【讨论】:

          【解决方案8】:

          dict type 有一个get function,如果字典中不存在该键,get 的第二个参数是它应该返回的值。同样有setdefault,如果key存在则返回dict中的值,否则根据你的默认参数设置值,然后返回你的默认参数。

          您可以扩展 list 类型以具有 getindexdefault 方法。

          class SuperDuperList(list):
              def getindexdefault(self, elem, default):
                  try:
                      thing_index = self.index(elem)
                      return thing_index
                  except ValueError:
                      return default
          

          然后可以像这样使用:

          mylist = SuperDuperList([0,1,2])
          index = mylist.getindexdefault( 'asdf', -1 )
          

          【讨论】:

            【解决方案9】:

            我对列表中的“.index()”方法也有同样的问题。我对它抛出异常这一事实没有意见,但我强烈不同意它是一个非描述性的 ValueError 的事实。不过,我可以理解它是否会是一个 IndexError。

            我明白为什么返回“-1”也是一个问题,因为它是 Python 中的有效索引。但实际上,我从不期望“.index()”方法返回负数。

            这里有一个单行(好吧,这是一个相当长的行...),只遍历列表一次,如果找不到该项目,则返回“None”。如果您愿意,将其重写为返回 -1 将是微不足道的。

            indexOf = lambda list, thing: \
                        reduce(lambda acc, (idx, elem): \
                               idx if (acc is None) and elem == thing else acc, list, None)
            

            使用方法:

            >>> indexOf([1,2,3], 4)
            >>>
            >>> indexOf([1,2,3], 1)
            0
            >>>
            

            【讨论】:

              【解决方案10】:

              我建议:

              if thing in thing_list:
                list_index = -1
              else:
                list_index = thing_list.index(thing)
              

              【讨论】:

              • 此解决方案的问题是“-1”是列表中的有效索引(最后一个索引;倒数第一个)。处理此问题的更好方法是在您的条件的第一个分支中返回 False。
              【解决方案11】:

              这个呢:

              otherfunction(thing_collection, thing)
              

              与其在函数接口中公开像列表索引这样依赖于实现的东西,不如传递集合和事物,让其他函数处理“成员资格测试”问题。如果其他函数被编写为与集合类型无关,那么它可能会以:

              if thing in thing_collection:
                  ... proceed with operation on thing
              

              如果 thing_collection 是列表、元组、集合或字典,这将起作用。

              这可能比:

              if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:
              

              这是你在其他函数中已有的代码。

              【讨论】:

                【解决方案12】:

                这个问题是语言哲学问题之一。例如,在 Java 中,一直有一个传统,即异常应该只用于“异常情况”,即发生错误时,而不是flow control。起初这是出于性能原因,因为 Java 异常很慢,但现在这已成为公认的风格。

                相比之下,Python 一直使用异常来表示正常的程序流程,比如我们在这里讨论的引发ValueError。在 Python 风格中,这并没有什么“肮脏”的地方,而且还有更多来自哪里。一个更常见的例子是StopIteration exception,它由迭代器的next() 方法引发,表示没有其他值。

                【讨论】:

                • 实际上,JDK 抛出了太多方式 的检查异常,所以我不确定这种哲学是否真的适用于 Java。我对StopIteration 本身没有问题,因为它明确定义了异常的含义。 ValueError 有点太笼统了。
                • 我指的是不应将异常用于流控制的想法:c2.com/cgi/wiki?DontUseExceptionsForFlowControl,与其说是 Java 具有的检查异常的数量,不如说是其他讨论:mindview.net/Etc/Discussions/CheckedExceptions跨度>
                【解决方案13】:
                thing_index = thing_list.index(elem) if elem in thing_list else -1
                

                一行。简单的。没有例外。

                【讨论】:

                • 简单,是的,但这将进行两次线性搜索,虽然性能本身不是问题,但这似乎过分了。
                • @Draemon:同意——这将执行 2 次传递——但从一千行代码库来看,这不太可能成为瓶颈。 :) 人们总是可以选择加入for 的命令式解决方案。
                • 带 lambda indexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
                【解决方案14】:

                使用ValueError 的代码没有任何问题。如果您想避免异常,这里还有另一个单行:

                thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
                

                【讨论】:

                • 那是python 2.6吗?我知道我没有提到它,但我使用的是 2.5。这可能是我在 2.6 中所做的
                • @Draemon:是的,next() 函数存在于 Python 2.6+ 中。但是2.5很容易实现,见next() function implementation for Python 2.5
                【解决方案15】:

                使用 try-except 子句并没有什么“脏”。这是pythonic的方式。 ValueError 将仅由 .index 方法引发,因为它是您拥有的唯一代码!

                回答评论:
                在 Python 中,easier to ask forgiveness than to get permission 哲学已经确立,no index 不会针对任何其他问题引发此类错误。不是我能想到的。

                【讨论】:

                • 当然例外是针对特殊情况的,但事实并非如此。如果异常比 ValueError 更具体,我就不会遇到这样的问题。
                • 我知道它只能从那个 method 抛出,但它是否保证只能从那个 reason 抛出?并不是说我能想到索引失败的另一个原因..但是对于那些你可能没有想到的事情来说,这不是例外吗?
                • {}.get(index, '') 不是更pythonic吗?更不用说更短更易读了。
                • 当我期望密钥存在时我使用 dict[key],当我不确定时我使用 dict.get(key),我 am 在这里寻找等效的东西.返回 None 而不是 -1 会很好,但是正如您自己评论的那样, str.find() 返回 -1 那么为什么不应该有 list.find() 做同样的事情呢?我不买“pythonic”论点
                • 但关键是最 Pythonic 的解决方案是使用 only try/except 而不是 -1 sentinel 值。 IE。你应该重写otherfunction。另一方面,如果它没有坏,...
                【解决方案16】:

                我不知道为什么你会认为它很脏……因为例外?如果你想要一个oneliner,这里是:

                thing_index = thing_list.index(elem) if thing_list.count(elem) else -1
                

                但我建议不要使用它;我认为 Ross Rogers 的解决方案是最好的,使用一个对象来封装你想要的行为,不要试图以可读性为代价将语言推向极限。

                【讨论】:

                • 是的,因为例外。您的代码将执行两个线性搜索,不是吗?在这里,性能并不重要。 SuperDuperList 解决方案很好,但在这种特殊情况下似乎有点矫枉过正。我想我最终会捕捉到异常,但我想看看是否有一种更清洁(符合我的审美)的方式。
                • @Draemon: 好吧,你将把你拥有的代码封装到find() 函数中,它会很干净;)
                • 奇怪的是,我的回答有两个反对意见,而 Emil Ivanov 的回答虽然在语义上相同,但却是最赞成的回答之一。很可能发生这种情况是因为我的速度较慢,因为我使用 count() 而不是“in”运算符……至少有一条评论说那会很棒,尽管 :-)
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-07-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-11-27
                相关资源
                最近更新 更多