【问题标题】:Python: Do Python Lists keep a count for len() or does it count for each call?Python:Python 列表是否对 len() 进行计数,还是对每次调用都进行计数?
【发布时间】:2021-11-30 13:29:20
【问题描述】:

如果我一直在一个很长的列表上调用 len(),我是在浪费时间,还是在后台保留一个 int 计数?

【问题讨论】:

  • 这总是困扰着我。特别是当我的分析器告诉我,我花了 2.2 秒的时间运行了一个运行一分钟的程序来调用这个函数。

标签: python


【解决方案1】:

别担心:它当然会节省计数,因此列表上的len() 是一个非常便宜的操作。顺便说一句,字符串、字典和集合也是如此!

【讨论】:

  • 我相信它会保留最后一项+1 或其他内容的索引之类的计数,并从该内存位置调用 len() 的值。我说的对吗?
【解决方案2】:

如果您不想自己下载源代码,另一种方法是 to look it up on Google Code Search look at the source on GitHub

static Py_ssize_t list_length(PyListObject *a)
{
    return a->ob_size;
}

【讨论】:

    【解决方案3】:

    【讨论】:

      【解决方案4】:

      编写您的程序,使其优化清晰且易于维护。致电len(foo),您的程序是否更清晰?然后这样做。

      您是否担心花费的时间?使用timeit module in the standard library测量所花费的时间,并查看它在您的代码中是否重要。

      与大多数人一样,您很可能会错误地猜测程序的哪些部分最慢。避免猜测的诱惑,而是通过测量来找出答案。

      请记住,用 Donald Knuth 的话来说,过早的优化是万恶之源。只关注您测量速度的代码速度,以了解改变其工作方式所带来的好处是否值得付出代价。

      【讨论】:

      • 我是一名刚入职几个月的程序员,我支持这两种做法。计时器的东西提供了两道防线,可以抵御大量的传统智慧(“SELECT.. RANDOM 太贵了”),它们会被扔到你的路上。
      【解决方案5】:

      问题已得到解答(len 为 O(1)),但您可以通过以下方式自行检查:

      $ python -m timeit -s "l = range(10)" "len(l)"
      10000000 loops, best of 3: 0.119 usec per loop
      $ python -m timeit -s "l = range(1000000)" "len(l)"
      10000000 loops, best of 3: 0.131 usec per loop
      

      是的,不是很慢。

      【讨论】:

        【解决方案6】:

        Python“列表”实际上是一个可调整大小的数组,而不是链表,因此它将大小存储在某个地方。

        【讨论】:

          【解决方案7】:

          我来晚了,但我刚刚做了一个测试,得到了一些有趣的结果:

          import timeit, statistics
          
          test_arr = list(range(1000000))
          
          # Just call len() every time
          def test1(arr):
              for i in range(len(arr)):
                  j = arr[len(arr)-1]
          
          # Store the result of len()
          def test2(arr):
              l = len(arr)
              for i in range(len(arr)):
                  j = arr[l-1]
          
          print("Running test 1...")
          t1 = timeit.repeat(lambda: test1(test_arr), number=int(2e2), repeat=4)
          print("Test 1 results:")
          print(t1)
          
          print("Running test 2...")
          t2 = timeit.repeat(lambda: test2(test_arr), number=int(2e2), repeat=4)
          print("Test 2 results:")
          print(t2)
          
          m1 = statistics.mean(t1)
          m2 = statistics.mean(t2)
          avg = round(100 * abs(m2-m1)/m1, 2)
          sign = "quicker" if m1-m2 > 0 else "slower"
          print(f"On average, test 2 was {avg} {sign} than test 1")
          

          结果:

          Running test 1...
          Test 1 results:
          [15.189714099979028, 15.160469999769703, 15.17295220005326, 15.072236399864778]
          Running test 2...
          Test 2 results:
          [8.17264029989019, 8.191439799964428, 8.171442999970168, 8.208112000022084]
          On average, test 2 was 45.96 quicker than test 1
          

          这是一个非常粗略的测试,我相信有人可以改进它(或指出错误),但似乎只调用一次 len 并将其保存到变量中确实要快得多,而不是而不是反复调用它(我相信这是发帖人所要求的)。

          【讨论】:

          • 我会说基准测试有点误导,因为它除了重复调用len()(或不调用它)之外没有其他实际工作。对于一个还可以做其他工作的函数,len() 的价格可能会消失......
          • 顺便说一句,您只需将全局 len 别名为本地,即可将 test1 的时间缩短约 17%。全局/模块查找在 CPython 中也很昂贵。
          • 真理出现在算法中。谢谢。
          【解决方案8】:

          它必须在某处存储长度,因此您不必每次都计算项目数。

          【讨论】:

          • 请注意,并非所有语言都如此。某些语言实际上使用链表,并且可能会或可能不会将元数据保留在大小左右。一个完美的例子是 Lisp,其中列表使用相对原始的 2 个单元结构来构建自己,没有元数据。在这些情况下,长度调用需要 O(n) 时间。
          猜你喜欢
          • 2019-07-17
          • 1970-01-01
          • 2012-09-06
          • 1970-01-01
          • 1970-01-01
          • 2018-07-21
          • 1970-01-01
          • 2019-03-20
          • 1970-01-01
          相关资源
          最近更新 更多