我学习编程语言,测验问题和解决方案是这样的:
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 ??
答案 0 :(得分:4)
另外两个答案很棒。我建议你试试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
的地址在您操作时保持不变,但在您使用=
时会发生变化。函数内部的变量赋值使变量本身变为函数
答案 1 :(得分:3)
你有很多事情要做。所以,让我们一步一步走。
x = [1,5]
您正在分配1,5到x的列表
y=foo(x)
你正在调用foo并传入x并指定从foo返回的任何内容
在foo
内调用x.append(3)
,它会将3附加到传入的列表中。
然后设置x = [8]
,它现在是对局部变量x的引用,然后从foo返回最终设置y = [8]
答案 2 :(得分:2)
理解这一点的关键是Python使用对象引用传递变量。这些类似于c ++这样的语言中的指针,但在非常关键的方面有所不同。
进行作业时(使用赋值运算符=
):
x = [1, 5]
实际上已经创造了两件事。首先是对象本身,它是列表[1, 5]
。该对象是第二个实体的独立实体,它是对象的(全局)对象引用。
Object(s) Object Reference(s)
[1, 5] x (global) <--- New object created
在python中,对象通过对象引用传递给函数;他们没有通过&#34;参考&#34;或者&#34;按价值&#34;就像在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
接下来,我们会做一些不同的事情。我们将local-foo x
对象引用(或重新分配,因为之前已存在的对象引用)分配给 NEW LIST 。
Object(s) Object Reference(s)
[1, 5, 3] x (global) <--- One object reference remains
[8] x (local to foo) <--- New object created
请注意,全局对象引用x
仍然存在!它没有受到影响。我们只将local-foo x
对象引用重新分配给新列表。
最后,应该很清楚,当函数返回时,我们有:
Object(s) Object Reference(s)
[1, 5, 3] x (global) <--- Unchanged
[8] y (global), x (local to foo) <--- New object reference created
请注意,local-foo x
对象引用仍然存在!这是理解的重要行为,因为如果我们做这样的事情:
def f(a = []):
a.append(1)
return a
f()
f()
f()
我们不会得到:
[1]
[1]
[1]
相反,我们会得到:
[1]
[1, 1]
[1, 1, 1]
语句a = []
仅在程序首次运行时由解释器评估ONCE,并且该对象引用永远不会被删除(除非我们删除函数本身)。
因此,当调用f()
时,local-f a
不会更改回[]
。它记得&#34;它以前的价值;这是因为本地对象引用仍然有效(即,它尚未被删除),因此该对象不会在函数调用之间收集垃圾。
对象引用与指针不同的一种方式是赋值。如果Python使用实际指针,您会期望以下行为:
a = 1
b = a
b = 2
assert a == 2
但是,这个断言会产生错误。原因是b = 2
不影响对象&#34;指向&#34;通过对象引用,b。它创建一个NEW对象(2
)并将b
重新分配给该对象。
对象引用与指针不同的另一种方式是删除:
a = 1
b = a
del a
assert b is None
此断言也会产生错误。原因与上面的例子相同; del a
不影响对象&#34;指向&#34;通过对象引用b
。它只是删除对象引用a
。对象引用b
及其指向的对象1
不受影响。
您可能会问,&#34;那么我该如何删除实际对象?&#34;答案是你可以做到的!您只能删除对该对象的所有引用。一旦不再引用任何对象,该对象就有资格进行垃圾回收,并且它将被删除(尽管您可以强制使用gc
模块)。此功能称为内存管理,它是Python的主要优势之一,也是Python首先使用对象引用的原因之一。
需要理解的另一个主题是有两种类型的对象:可变对象和不可变对象。可以更改可变对象,而不能更改不可变对象。
list
,例如[1, 5]
,是可变的。另一方面,tuple
或int
是不可变的。
append
方法现在所有这一切都很明确,我们应该能够直截了当地回答问题&#34; append
如何在Python中工作?&#34; append()
方法的结果是对可变列表对象本身的操作 。 list
已更改&#34;就位#34;可以这么说。没有创建新的list
,然后将其分配给foo-local x
。这与赋值运算符=
形成对比,赋值运算符{{1}}创建一个NEW对象并将该对象分配给对象引用。
答案 3 :(得分:1)
append
函数修改传递给函数的x
,而为x
分配新内容会更改本地范围的值并返回它。
答案 4 :(得分:1)
x
内foo
的范围特定于函数,并且独立于主调用上下文。 x
内的foo
开始在主上下文中引用与x
相同的对象,因为这是传入的参数,但是当您使用赋值运算符将其设置为{{ 1}}您已经分配了[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
(你看到的ID会有所不同,但我希望这一点仍然明确)
您可以看到Inside foo(): id(x) = 4418625336
After appending: id(x) = 4418625336
After setting x to [8], id(x) = 4418719896
[1, 5, 3]
[8]
只是改变现有对象 - 无需分配新对象。但是一旦append
运算符执行,就会分配一个新对象(并最终返回到主上下文,并将其分配给=
)。