我们都知道,教条全局变量是不好的。 当我开始学习Python我读传递给函数的参数作为的Funktion内局部变量处理。 这似乎是真理的至少一半:
def f(i):
print("Calling f(i)...")
print("id(i): {}\n".format(id(i)))
print("Inside f(): i += 1")
i += 1
print("id(i): {}".format(id(i)))
return
i = 1
print("\nBefore function call...")
print("id(i): {}\n".format(id(i)))
f(i)
这个计算结果为:
Before function call...
id(i): 507107200
Calling f(i)...
id(i): 507107200
Inside f(): i += 1
id(i): 507107232
正如我现在读的功能在Python中调用的机制是“打电话的对象引用”。 这意味着一个参数最初通过它的对象引用传递,但如果是在函数内部修改,创建一个新的对象变量。 这似乎是合理的,我以避免设计中的功能无意中修改全局变量。
但是,如果我们传递一个列表作为参数,会发生什么?
def g(l):
print("Calling f(l)...")
print("id(l): {}\n".format(id(l)))
print("Inside f(): l[0] += 1")
l[0] += 1
print("id(l): {}".format(id(l)))
return
l = [1, 2, 3]
print("\nBefore function call...")
print("id(l): {}\n".format(id(l)))
g(l)
这导致:
Before function call...
id(l): 120724616
Calling f(l)...
id(l): 120724616
Inside f(): l[0] += 1
id(l): 120724616
正如我们所看到的,对象引用保持不变! 因此,我们在全局变量工作,不是吗?
我知道我们可以很容易地通过传递列表的副本与功能解决这个问题:
g(l[:])
但我的问题是:什么是在Python中实现的功能参数的两个不同的行为的原因? 如果我们要操纵一个全局变量,我们也可以使用列表中的“全局” - 关键字就像我们会为整数做,难道不是吗? 这是如何的行为与Python的禅一致“明确优于隐式”?
Python有两种类型的对象 - 可变和inmutable。 大多数内置的类型,如int,字符串或浮点数,是inmutable。 这意味着他们不能改变。 像列表,字典或数组类型是可变的,这意味着它们的状态可以改变。 几乎所有的用户定义的对象是可变的了。
当你做i += 1
,你一个新的值赋给我,这是i + 1
。 这不会发生变异我以任何方式,它只是说,它应该忘了我,并用值来代替它的i + 1
。 然后i
变成由一个完全新的对象替代。 但是,当你这样做i[0] += 1
在列表中,你是应该更换滤芯0列表说i[0] + 1
。 这意味着, id(i[0])
将与新的对象来改变和列表的状态,我会改变,但它的身份仍然是相同的-这是同一个对象它,只是改变了。
需要注意的是在Python这不是真正的字符串,因为他们是不可改变的,改变一个元素将复制使用更新的值的字符串,并创建新的对象。
为什么INT和列表功能参数区别对待?
他们不是。 所有参数都同等对待,不分类型。
您所看到的两种情况之间不同的行为,因为你正在做不同的事情, l
。
首先,让我们简化+=
成=
和+
: l = l + 1
在第一种情况下,和l[0] = l[0] + 1
中的第二个。 ( +=
并不总是等于分配和+
;这取决于运行时类上左侧的对象,它可以覆盖它的;但是在这里,为int
s时,相当于一个分配和+
。)亦,转让的右侧只是读的东西,是不感兴趣,所以我们就忽略它; 所以你有了:
l = something (in the first case)
l[0] = something (in the second case)
第二个是“分配给一个元素”,这实际上是对方法的调用的语法糖. __setitem__()
. __setitem__()
l.__setitem__(0, something)
所以,现在你可以看到两者之间的区别 -
- 在第一种情况下,你是分配给可变
l
。 Python是通过按值,所以这对外部的代码没有影响。 分配给变量简单地使其指向一个新的对象; 它有它用来指向的对象没有影响。 如果您已经分配的东西l
在第二种情况下,它也将有原来的对象没有任何影响。 - 在第二种情况下,要调用的方法的对象指向
l
。 这种方法恰好是列出了不同诱变方法等修改了该列表对象的内容,原始列表对象,向其中在传递给该方法的指针。 这是事实, int
(在运行时类的l
在第一种情况),恰好有没有方法被变异,但是这是除了点。
如果你做了同一件事l
在这两种情况下(如果这是可能的),那么你可以期望相同的语义。
这是跨越了一堆的语言很常见(红宝石,例如)。
变量本身的作用范围是功能。 但是,变量只是一个指向对象漂浮在内存的某个地方-而该对象可以改变的。
在Python一切都是对象,因此一切通过引用表示。 大约在Python变量最显着的一点是,它们所包含的对象,而不是对象本身的引用。 现在,当参数传递给函数,它们是通过引用传递。 因此,在函数的范围内,每一个参数被分配给自变量的参考,然后作为函数内的局部变量处理。 当你分配一个新值参数,要更改其引用的对象,所以你有一个新的对象,并给它的任何变化(即使它是一个可变对象)都不会被看到的功能在范围之外问题,反正不涉及到传递的参数。 这就是说,当你不分配一个新的参考参数,它保持控股的说法参考,并对其进行任何更改(当且仅当它是可变的)将在功能范围之外看到。