我学习编程语言,有一个问题和解法如下:
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??
我学习编程语言,有一个问题和解法如下:
def foo(x):
x.append (3)
x = [8]
return x
x=[1, 5]
y= foo(x)
print x
print y
[1 5 3 ]
[8]
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
的地址保持不变,但在使用=
时会发生改变。在函数内部进行变量赋值会使该变量成为局部变量。你有很多事情需要处理,所以我们一步一步来。
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。
理解这一点的关键在于Python使用对象引用来传递变量。这类似于C++等语言中的指针,但在很关键的方面有所不同。
当进行赋值操作(使用赋值运算符=
)时:
x = [1, 5]
实际上创建了两个东西。第一个是对象本身,即列表[1, 5]
。这个对象是与第二个东西分开的,第二个是指向该对象的(全局)对象引用。
Object(s) Object Reference(s)
[1, 5] x (global) <--- New object created
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
是可变的。tuple
或int
则是不可变的。
append
方法现在这一切都清楚了,我们应该能够直观地回答问题"Python中的append
如何工作?"append()
方法的结果是对可变列表对象本身的操作。可以说,list
在原地被改变。没有创建新的list
,然后将其分配给foo-local x
。这与赋值运算符=
相反,它创建一个新对象,并将该对象分配给对象引用。
foo
函数内的x
的作用域仅限于该函数,并与主调用上下文无关。x
在foo
内部开始引用与主上下文中的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
)。
append
函数修改了传入函数的x
,而将新值赋给x
会改变本地作用域的值并返回它。
print x
时,因为这时会附加一个3
。你在函数内部改变了x
(因为它是可变列表),但是然后你重新分配了x
给其他东西。旧的x
仍然存在(并且已经改变了),新的x
被返回(并分配给y
)。 - user707650