【问题标题】:How does Python process lists?Python如何处理列表?
【发布时间】:2013-10-28 23:25:41
【问题描述】:

我有一个清单:

list1 = [1,2,3]

如果我使用一个函数来获取一些我想在原始列表中替换的数据

new_data = [2,3,4]

为什么不

list1 = new_data

改变原始列表?为什么它会创建一个新的引用?

list1[:] = new_data 

确实有效,但为什么其他表达式无效?

【问题讨论】:

标签: python arrays list


【解决方案1】:

因为这不是 Python 的工作方式。 (什么语言像这样工作?)

Python 变量名称就是:名称。分配foo = whatever 只会使foowhatever 命名的对象创建一个新名称。简单的赋值永远不会改变现有的对象。

【讨论】:

  • “什么语言可以这样工作?”:例如,C++ 就有这种行为。如果你有一个std::vector<int> list1;,然后分配list1 = new_data,那么分配给list1的向量会得到new_data内容的副本。
  • 我发誓 90% 的初学者对 Python 的困惑是由 C++ 中的疯狂造成的
  • @minitech cplusplus.com/reference/vector/vector/operator= c++ 完全有这种行为。它允许重载赋值。
  • 好的,我现在明白那个评论了!如果我们谈论的是可能性,VB.NET 和 C# 也可以分别使用 ByRefref 来实现。 (虽然不完全一样。)
  • 通过引用传递仍然只会改变存储桶(变量),而不是列表本身。 c++ 在模糊这两个概念之间的界限方面可能是独一无二的。
【解决方案2】:

Python 的名称是对象上的标签,而不是内存位置。这与 C++ 非常不同。一个对象可能有多个名称,如果它包含在某个其他对象(如列表)中,则可能根本没有名称。

简单的赋值不会改变对象,它们只会重新绑定名称。以前绑定到 list1 的对象不会更改,但如果该名称是程序中引用它的唯一方式,它可能会被垃圾回收。

网页Code like a Pythonista 很好地解释了这一点。如果您想更好地理解 Python 变量,我会检查一下。

【讨论】:

    【解决方案3】:

    因为这就是 Python 中赋值的工作方式。 所有 赋值更改指定名称以引用其他对象而不是它们已经引用的对象。

    【讨论】:

      【解决方案4】:

      Python 名称只是对内存中某个位置的引用。所以:

      list1 = new_data
      

      只是让两个变量引用内存中的相同位置。

      另一方面,list1[:] 复制了 list1

      【讨论】:

      • 分配给list1[:] 不会复制list1
      • 我没有说给 list1[:] 赋值,只是说 list1[:] 复制了它。
      • 但问题是分配给list1list1[:] 之间的区别,所以你应该提一下区别。
      【解决方案5】:

      对于 C 程序员:C 变量是 指针,python 变量是 句柄。我认为你真的想做这样的事情,但是在 python 中:

      // C code
      std::vector<int> myvector;
      
      myfunction(std::vector<int> &testvector) {
           if (somearg) {
               testvector.append(4)
           } else {
               int[] myints = {4,15,16};
               testvector = std::vector<int>(myints)
           }
      }
      

      因为 testvector 是一个指向对象的指针,并且您更改了它指向的对象,所以无论您走哪条路径,父级都会看到您的更改。

      在 python 中,这看起来像这样:

      list1 = [1,2,3]
      
      def modfunc(mylist):
          if (somearg):
              mylist.append(4)
          else:
              mylist = [1,2,3]
      

      虽然第一个会起作用,但第二个不会。在这种情况下, mylist 不是直接指向内存的指针;它指向对象查找表中的一个条目,然后指向真实对象。在失败的情况下,您将名称 mylist 更改为指向不同的对象,但父对象的名称仍指向原始对象。

      在它工作的第一种情况下,您实际上尊重名称和对象列表以获取实际对象并直接操作它。父母和孩子的名字都指向这个对象,所以它可以工作。

      那么,你是做什么的?好吧,简而言之,您不需要这样做。在 C 中,您经常需要引用,因为它将您限制为单个返回值。当然,您可以使用结构,但在 C 中这样做不是很方便。在 python 中,元组是该语言的自然组成部分。所以假设你想在 C 中做这样的事情:

       int sumdefault(std::vector<int> &avector):
           if len(avector) == 0:
               int[] someints = {1,2,3,4,5}
               avector = std::vector<int>(someints);
           return sum(avector)
      

      因此,您需要 int 返回值来返回总和。你也可能会改变一个向量,所以你需要返回一个引用。此外,返回 avector(例如,成对的)可能很危险,因为您正在本地堆栈上创建一个变量,因此返回对它的引用实际上是无效的,但按值返回可能很昂贵(并且不必要)如果avector很大,yada yada。在 python 中,您只需返回两个值:

      def sumdefault(mylist=[]):
          if len(mylist) == 0:
              mylist = [1,2,3,4,5]
          return mylist, sum(mylist)
      
       alist = [2,3,4,5]
       alist, sumalist = sumdefault(alist)
      

      这是(afaik)处理这种模式的正确pythonic方法。你永远不会浪费时间不必要地复制列表——Python 总是传递对事物的引用。并且 Python 确实没有像 C 那样的“局部”变量:在子函数中创建的变量具有局部 name,但它位于全局堆中,所以即使我们在子函数中构造[1,2,3,4,5],当我们返回一个函数时,该内存不会消失 - 我们的本地名称将消失,但父函数现在将有一个指向它的名称,它会一直存在正如某些名称所引用的那样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-23
        • 1970-01-01
        • 2013-11-05
        • 1970-01-01
        • 1970-01-01
        • 2021-04-03
        • 2017-09-27
        相关资源
        最近更新 更多