【问题标题】:Why does 'remove' mutate a list when called locally? [duplicate]为什么在本地调用时“删除”会改变列表? [复制]
【发布时间】:2012-04-23 02:25:52
【问题描述】:

可能重复:
Understanding Python's call-by-object style of passing function arguments

我最近遇到了这个:

x = [1,2,3]

def change_1(x):
    x = x.remove(x[0])
    return x

结果:

>>> change_1(x)
>>> x
[2, 3]

我发现这种行为令人惊讶,因为我认为函数内部的任何内容都不会影响外部变量。此外,我构建了一个示例,它基本上做同样的事情,但没有使用remove

x = [1,2,3]

def change_2(x):
    x = x[1:]
    return x

结果:

>>> change_2(x)
[2, 3] # Also the output prints out here not sure why this is
>>> x
[1, 2, 3]

我得到了我期望的结果,函数并没有改变 x。

所以必须是 remove 特定的东西才有效。这是怎么回事?

【问题讨论】:

  • Python tutorial 4.6,第 6 段(另请参阅脚注)。这些东西实际上在教程中都有说明。
  • 您认为x.remove(x[0]) 会返回什么值?它实际上返回什么?改变对象和将现有名称重新绑定到新创建的对象有什么区别?
  • 也许,一个让你看一些例子,做一些练习来完全掌握语言工作原理的好地方是learnpythonthehardway.org
  • 很明显,Akavall 了解局部变量以及将列表传递给函数的语义。 (为什么每个人都认为他没有!)。他基本上已经为自己回答了这个问题:他的怀疑是正确的:这是remove 特有的东西。 remove 是一种破坏性操作(它会改变对象)。切片提取没有破坏性;它计算一个与原始列表类似的新列表,但删除了部分。就是这样。

标签: python list


【解决方案1】:

令人困惑的一件事是,您将许多不同的事物称为“x”。例如:

def change_1(x):       # the x parameter is a reference to the global 'x' list
    x = x.remove(x[0]) # on the left, though, is a new 'x' that is local to the function
    return x           # the local x is returned

>>> x = [1, 2, 3]
>>> y = change_1(x)    # assign the return value to 'y'
>>> print y
None                   # this is None because x.remove() assigned None to the local 'x' inside the function
>>> print x
[2, 3]                 # but x.remove() modified the global x inside the function

def change_2(x):
    x = x[1:]          # again, x on left is local, it gets a copy of the slice, but the 'x' parameter is not changed
    return x           # return the slice (copy)

>>> x = [1, 2, 3] 
>>> y = change_2(x)
>>> print x
[1, 2, 3]             # the global 'x' is not changed!
>>> print y
[2, 3]                # but the slice created in the function is assigned to 'y'

【讨论】:

  • 您的提示字符串在错误的行上 ;)
【解决方案2】:

如果您调用函数的参数nq,您将得到相同的结果。

受到影响的不是变量名。列表范围内的 x 和该范围外的 x 是两个不同的“标签”。由于您将附加x 的值传递给change_1(),但是它们都指的是同一个对象。当您对函数中的对象执行x.remove() 时,您基本上是在说:“获取x 所指的对象。现在从该对象中删除一个元素。这与lhs 分配非常不同。如果你做了@ 987654329@ 在你的函数中。你没有对列表对象做任何事情。你基本上只是撕下[1, 2. 3] 的'x'标签并将它放在任何 y 指向的东西上。 p>

【讨论】:

    【解决方案3】:

    您的变量 x 只是对您创建的列表的引用。当您调用该函数时,您将按值传递该引用。但是在函数中,你有一个对同一个列表的引用所以当函数修改它时,它是在任何范围内修改的。

    另外,在交互式解释器中执行命令时,python 会打印返回值,如果它没有分配给变量。

    【讨论】:

    • 术语狡辩:它不是通过引用传递的;它是按值传递的引用类型。通过引用传递意味着您可以在函数内部为 x 分配另一个列表,并且更改将反映在调用者的范围内。
    • 已编辑以反映您的评论。谢谢你!
    • 我认为它不是按引用传递的按值传递——Python 使用 Liskov 所说的“对象共享调用”,这是一个单独的概念。关键是 Python 变量,虽然表面上很相似,但一点也不像 C 风格语言中的变量。
    • 我看不出“通过对象共享调用”与通过值传递引用类型(即值是对对象的引用)有何不同,但我可能遗漏了一些微妙之处.我也不确定变量是如何在这里发挥作用的,因为调用者中的变量 x 与函数中的 x 参数是不同的变量。 AFAIK,这一切都与它在 Ruby 或 C# 中的工作方式相同。
    • @IsaacCambron:你说得对,差异充其量只是微妙的。我主要更喜欢“对象共享调用”术语,因为它比“值是引用的值调用”更简洁,并且不太可能与“引用调用”混淆,这是一个不同的概念,其中“引用”一词" 甚至不是同一个意思。确实,Python 的模型与 Ruby 的模型几乎相同,但要小心与 C# 的比较,因为 C# 由于值类型和引用类型的二分法而增加了一些皱纹。
    猜你喜欢
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-02
    • 1970-01-01
    • 2015-11-22
    相关资源
    最近更新 更多