Python:为什么是int&列表函数参数区别对待?

时间:2014-11-06 09:44:04

标签: python function parameters globals

我们都知道全局变量不好的教条。当我开始学习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中函数的调用机制是"按对象引用调用"。这意味着参数最初由它的对象引用传递,但如果在函数内部修改它,则会创建 new 对象变量。对于我来说,避免设计函数无意中修改全局变量似乎是合理的。

但是如果我们将列表作为参数传递会发生什么?

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中实现函数参数的两种不同行为的原因是什么?如果我们打算操纵一个全局变量,我们也可以使用" global" -keyword作为列表,就像我们对整数一样,不是吗?这种行为如何与python" zen的zen一致,是否优于隐式"?

4 个答案:

答案 0 :(得分:1)

Python有两种类型的对象 - 可变和不可变。大多数内置类型(如int,string或float)都是不可变的。这意味着他们无法改变。 list,dict或array之类的类型是可变的,这意味着它们的状态可以改变。几乎所有用户定义的对象都是可变的。

执行i += 1时,为i分配一个新值,即i + 1。这不会以任何方式改变我,它只是说它应该忘记我并用值i + 1替换它。然后i被一个全新的对象取代。 但是当您在列表中执行i[0] += 1时,您会对列表说明应该用i[0] + 1替换元素0。这意味着id(i[0])将使用新对象进行更改,并且列表i的状态将更改,但它的标识保持不变 - 它是相同的对象,只是更改。

请注意,在Python中,字符串不是这样,因为它们是不可变的,更改一个元素将使用更新的值复制字符串并创建新对象。

答案 1 :(得分:1)

  

为什么选择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__()的调用的语法糖:

l.__setitem__(0, something)

所以现在你可以看到两者之间的区别 -

  • 在第一种情况下,您分配变量l。 Python是按值传递的,因此这对外部代码没有影响。分配给变量只是使它指向一个新对象;它对它曾经指向的对象没有影响。如果您在第二种情况下已将某些内容分配给l,则它也不会对原始对象产生任何影响。
  • 在第二种情况下,您在<{1}}指向的对象上调用方法。这种方法恰好是列表上的一种变异方法,因此修改了列表对象的内容,原始列表对象是一个传递给方法的指针。确实l(第一种情况下的int的运行时类)碰巧没有变异的方法,但除了这一点之外。

如果你在两种情况下都对l做了同样的事情(如果可能的话),那么你可以期待相同的语义。

答案 2 :(得分:0)

这在很多语言中很常见(例如Ruby)。

变量本身的范围是函数。但是这个变量只是一个指向内存中浮动的对象的指针 - 而且可以改变对象。

答案 3 :(得分:0)

在Python中,一切都是一个对象,因此一切都通过引用来表示。 Python中变量最值得注意的是它们包含对象的引用,而不是对象本身。现在,当参数传递给函数时,它们通过引用传递。因此,在函数范围内,每个参数都被赋值给参数的引用,然后被视为函数内的局部变量。为参数分配新值时,您正在更改它引用的对象,因此您有一个新对象,并且对它的任何更改(即使它是一个可变对象)都不会在函数范围之外看到问题,而且与传递的论点无关。也就是说,当你没有为参数赋予一个新的引用时,它会保留参数的引用,并且对它的任何更改(当且仅当它是可变的)才会在函数范围之外看到。