我有一个清单:
list1 = [1,2,3]
如果我使用一个函数来获取一些我想在原始列表中替换的数据
new_data = [2,3,4]
为什么不
list1 = new_data
改变原始列表?为什么它会创建一个新的引用?
list1[:] = new_data
确实有效,但为什么其他表达式不起作用?
因为这不是 Python 的工作方式。(什么语言可以这样工作?)
Python 变量名就是这样:名字。分配foo = whatever
只是foo
为由 命名的对象创建一个新名称whatever
。简单赋值永远不会改变现有对象。
Python 的名称是对象上的标签,而不是内存位置。这与 C++ 非常不同。一个对象可能有很多名称,或者如果它包含在某个其他对象(如列表)中,则根本没有名称。
简单的赋值不会改变对象,它们只会重新绑定名称。以前绑定的对象list1
不会更改,但如果该名称是程序中引用它的唯一方式,它可能会被垃圾回收。
像 Pythonista这样的网页代码很好地解释了这一点。如果您想更好地理解 Python 变量,我会检查一下。
因为这就是 Python 中赋值的工作方式。所有分配都会更改指定名称以引用其他对象,而不是它们已经引用的对象。
Python 名称只是对内存中某个位置的引用。所以:
list1 = new_data
只是让两个变量都引用内存中的相同位置。
另一方面,list1[:]
复制 list1
对于 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 那样的“局部”变量:在子函数中创建的变量具有局部名称,但它位于全局堆中,因此即使我们[1,2,3,4,5]
在子函数中构造,该内存当我们返回一个函数时不会消失 - 我们的本地名称将消失,但父函数现在将有一个指向它的名称,并且只要某些名称引用它,它就会一直存在。