Python中的追加方法是如何工作的?

3

我学习编程语言,有一个问题和解法如下:

def foo(x):
   x.append (3)
   x = [8]
   return x

x=[1, 5]
y= foo(x)
print x
print y

为什么这段代码会输出以下结果:
[1 5 3 ]
[8]

为什么 x 不等于 8??

2
@AdamSmith 除了当你 print x 时,因为这时会附加一个 3。你在函数内部改变了 x(因为它是可变列表),但是然后你重新分配了 x 给其他东西。旧的 x 仍然存在(并且已经改变了),新的 x 被返回(并分配给 y)。 - user707650
请查看如何通过引用传递变量。希望能对您有所帮助! - trantu
5个回答

6
另外两个答案很好。我建议您尝试使用id获取地址。
请参考以下示例。
def foo(x):
   x.append (3)
   print "global",id(x)
   x = [8]
   print "local ",id(x)
   return x

x=[1, 5]
print "global",id(x)
y= foo(x)
print "global",id(x)
print x
print y

输出结果

global 140646798391920
global 140646798391920
local  140646798392928
global 140646798391920
[1, 5, 3]
[8]

正如您所看到的,当您操纵变量时,x的地址保持不变,但在使用=时会发生改变。在函数内部进行变量赋值会使该变量成为局部变量。

5

你有很多事情需要处理,所以我们一步一步来。

x = [1,5] 你将一个列表[1,5]赋值给x

y=foo(x) 你调用了foo函数并传入了x,然后将foo返回的任何内容赋值给y

foo函数内部,你调用了x.append(3),它将3添加到传入的列表中。

然后你设置了x = [8],现在x是指向本地变量x的引用,最终将y = [8]赋值给x。


4

对象引用

理解这一点的关键在于Python使用对象引用来传递变量。这类似于C++等语言中的指针,但在很关键的方面有所不同。

当进行赋值操作(使用赋值运算符=)时:

x = [1, 5]

实际上创建了两个东西。第一个是对象本身,即列表[1, 5]。这个对象是与第二个东西分开的,第二个是指向该对象的(全局)对象引用。

Object(s)       Object Reference(s)
[1, 5]          x (global)                    <--- New object created

在Python中,对象是通过对象引用传递到函数中的;它们不像C++那样按“按引用”或“按值”传递。这意味着当将x传递到foo函数时,会创建一个新的本地对象引用指向该对象。
Object(s)       Object Reference(s)
[1, 5]          x (global), x (local to foo)  <--- Now two object references

现在,在foo函数内部,我们调用x.append(3),它会直接更改对象(即foo-local x对象引用所指向的对象)本身。
Object(s)       Object Reference(s)
[1, 5, 3]       x (global), x (local to foo)  <--- Still two object references

接下来,我们要做点不同寻常的事情。我们将本地变量 x 对象引用(或重新分配已经存在的对象引用)赋值给一个新列表

Object(s)       Object Reference(s)
[1, 5, 3]       x (global)                    <--- One object reference remains
[8]             x (local to foo)              <--- New object created

请注意,全局对象引用x仍然存在!它没有受到影响。我们只是重新分配了本地foo x对象引用到一个新列表。
最后,应该清楚的是,当函数返回时,我们有:
Object(s)       Object Reference(s)
[1, 5, 3]       x (global)                    <--- Unchanged
[8]             y (global), x (local to foo)  <--- New object reference created

侧边栏

注意本地变量 x 的引用仍然存在!这是很重要的行为需要理解,因为如果我们像下面这样做:

def f(a = []):
    a.append(1)
    return a
f()
f()
f()

我们不会获得:

[1]
[1]
[1]

相反,我们将得到:
[1]
[1, 1]
[1, 1, 1]

当程序第一次运行时,解释器仅对语句a = []进行一次求值,并且该对象引用永远不会被删除(除非我们删除函数本身)。

因此,当调用f()时,局部变量a不会被改回[]。它“记得”其先前的值;这是因为该局部对象引用仍然有效(即它尚未被删除),因此在函数调用之间,对象不会被垃圾回收。

与指针的对比

对象引用与指针的不同之处之一在于赋值。如果Python使用实际指针,则会出现以下行为:

a = 1
b = a
b = 2
assert a == 2

然而,这个断言是错误的。原因在于,b = 2 不影响对象引用所指向的对象,它创建了一个新对象(2),并将b重新赋值为该对象。
另外,对象引用与指针的另一个不同之处在于删除:
a = 1
b = a
del a
assert b is None

这个断言会产生一个错误。原因与上面的示例相同;del a不影响对象引用所指向的对象",b。它只是删除了对象引用a。对象引用b和它指向的对象1没有受到影响。

您可能会问:"那么我如何删除实际的对象呢?"答案是你不能!你只能删除所有对该对象的引用。一旦不再有任何对对象的引用,该对象就变得可以进行垃圾回收,并且将为您删除它(尽管您可以使用gc模块强制执行此操作)。这个特性被称为内存管理,它是Python的主要优势之一,也是Python首先使用对象引用的原因之一。

可变性

还需要理解的另一个主题是,有两种类型的对象:可变和不可变。可变对象可以更改,而不可变对象不能更改。

例如[1, 5]这样的list是可变的。tupleint则是不可变的。

append方法

现在这一切都清楚了,我们应该能够直观地回答问题"Python中的append如何工作?"append()方法的结果是对可变列表对象本身的操作。可以说,list在原地被改变。没有创建新的list,然后将其分配给foo-local x。这与赋值运算符=相反,它创建一个新对象,并将该对象分配给对象引用。


谢谢!你知道有赏金哦。 ;) - Rick

2

foo函数内的x的作用域仅限于该函数,并与主调用上下文无关。 xfoo内部开始引用与主上下文中的x相同的对象,因为这是传递的参数,但是一旦使用赋值运算符将其设置为[8],您已经为x内部的foo指向了一个新的对象,现在与主上下文中的x完全不同。 为了进一步说明,请尝试将foo更改为以下内容:

def foo(x):
   print("Inside foo(): id(x) = " + str(id(x)))
   x.append (3)
   print("After appending: id(x) = " + str(id(x)))
   x = [8]
   print("After setting x to [8], id(x) = " + str(id(x)))
   return x

当我执行时,会得到以下输出:
Inside foo(): id(x) = 4418625336
After appending: id(x) = 4418625336
After setting x to [8], id(x) = 4418719896
[1, 5, 3]
[8]

你看到的ID可能会有所不同,但我希望重点仍然清晰。

你可以看到append只是改变了现有对象 - 不需要分配一个新对象。 但是一旦执行=运算符,就会分配一个新对象(最终返回到主上下文中,然后被分配给y)。


我不知道怎么回答,我在你之前37秒已经回答了 :) .+1 - Bhargav Rao

2
< p > append 函数修改了传入函数的x,而将新值赋给x会改变本地作用域的值并返回它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接