代码之家  ›  专栏  ›  技术社区  ›  FMc TLP

为什么函数可以修改调用方所感知的某些参数,而不能修改其他参数?

  •  141
  • FMc TLP  · 技术社区  · 15 年前

    我试图理解Python对变量范围的方法。在这个例子中,为什么 f() 能够改变 x ,正如在 main() 但不是 n ?

    def f(n, x):
        n = 2
        x.append(4)
        print('In f():', n, x)
    
    def main():
        n = 1
        x = [0,1,2,3]
        print('Before:', n, x)
        f(n, x)
        print('After: ', n, x)
    
    main()
    

    输出:

    Before: 1 [0, 1, 2, 3]
    In f(): 2 [0, 1, 2, 3, 4]
    After:  1 [0, 1, 2, 3, 4]
    
    10 回复  |  直到 6 年前
        1
  •  176
  •   Aran-Fey Swapnil    6 年前

    有些答案在函数调用的上下文中包含单词“copy”。我觉得很困惑。

    python不复制 物体 在函数调用期间传递 曾经 .

    函数参数为 姓名 . 当调用函数时,python将这些参数绑定到您传递的任何对象(通过调用方作用域中的名称)。

    对象可以是可变的(如列表)或不可变的(如整数、Python中的字符串)。可以更改的可变对象。不能更改名称,只需将其绑定到另一个对象即可。

    你的例子不是关于 scopes or namespaces 是关于 naming and binding mutability of an object 在蟒蛇中。

    def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
        n = 2    # put `n` label on `2` balloon
        x.append(4) # call `append` method of whatever object `x` is referring to.
        print('In f():', n, x)
        x = []   # put `x` label on `[]` ballon
        # x = [] has no effect on the original list that is passed into the function
    

    这是几张漂亮的照片 the difference between variables in other languages and names in Python .

        2
  •  14
  •   Ethan Furman    13 年前

    你已经有了很多答案,我大致同意J.F.Sebastian的观点,但你可能会发现这是一个很有用的捷径:

    任何时候你看到 varname = ,您正在创建 新的 函数范围内的名称绑定。无论什么价值 varname 以前注定是迷路的 在这个范围内 .

    任何时候你看到 varname.foo() 您正在调用上的方法 变量名 . 方法可以更改varname(例如 list.append ) 变量名 (或者更确切地说,是指 变量名 名称)可能存在于多个作用域中,并且由于它是同一对象,因此任何更改都将在所有作用域中可见。

    [注意到 global 关键字创建第一个事例的异常]

        3
  •  12
  •   Konrad Rudolph    6 年前

    f 实际上不会改变 x (对列表实例的引用总是相同的)。相反,它改变了 内容 这个列表。

    在这两种情况下,a 参考文件副本 传递给函数。在函数内部,

    • n 获取分配的新值。只修改函数内部的引用,而不修改函数外部的引用。
    • X 未分配新值:函数内部或外部的引用均未修改。相反, X 艾斯 价值 被修改。

    既然两者都 X 在函数内部和外部引用相同的值,都可以看到修改。相比之下, n 功能内部和外部指 不同的 后值 n 在函数内部重新分配。

        4
  •  4
  •   Pitarou    15 年前

    我将重命名变量以减少混淆。 n -gt; 核因子 恩曼 . X -gt; XF XMAND :

    def f(nf, xf):
        nf = 2
        xf.append(4)
        print 'In f():', nf, xf
    
    def main():
        nmain = 1
        xmain = [0,1,2,3]
        print 'Before:', nmain, xmain
        f(nmain, xmain)
        print 'After: ', nmain, xmain
    
    main()
    

    当你调用函数时 f ,python运行时复制 XMAND 并将其分配给 XF ,并以类似方式分配 恩曼 核因子 .

    在情况下 n ,复制的值为1。

    在情况下 X 复制的值是 字面列表 〔0, 1, 2,3〕 . 这是一个 参考 到那个名单。 XF XMAND 指向同一列表,因此当您修改 XF 你也在修改 XMAND .

    但是,如果你要写如下的东西:

        xf = ["foo", "bar"]
        xf.append(4)
    

    你会发现的 XMAND 未更改。这是因为,排队 xf=[“foo”,“bar”] 你有改变 XF 指向一个 新的 名单。您对此新列表所做的任何更改都不会对该列表产生任何影响, XMAND 仍然指向。

    希望有帮助。-)

        5
  •  2
  •   Luiz Damim    15 年前

    因为列表是可变对象。如果不将x设置为[0,1,2,3]的值,则将重新定义对象[0,1,2,3]的标签。

    您应该这样声明函数f():

    def f(n, x=None):
        if x is None:
            x = []
        ...
    
        6
  •  2
  •   Jason Coon    15 年前

    n是一个int(不可变),一个副本传递给函数,所以在函数中,您要更改副本。

    x是一个列表(可变),以及 指示者 传递给函数,因此x.append(4)更改列表的内容。但是,您在函数中说x=[0,1,2,3,4]时,不会更改main()中x的内容。

        7
  •  1
  •   jouell    7 年前

    如果用完全不同的变量重新编写函数,我们调用 id 在他们身上,它很好地说明了这一点。我一开始并没有收到这个消息,我读了JFS的帖子 great explanation 所以我试图理解/说服自己:

    def f(y, z):
        y = 2
        z.append(4)
        print ('In f():             ', id(y), id(z))
    
    def main():
        n = 1
        x = [0,1,2,3]
        print ('Before in main:', n, x,id(n),id(x))
        f(n, x)
        print ('After in main:', n, x,id(n),id(x))
    
    main()
    Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
    In f():                          94635800628384 139808499830024
    After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024
    

    Z和X具有相同的ID。正如本文所说,相同的底层结构具有不同的标记。

        8
  •  0
  •   ncmathsadist    13 年前

    如果您正确地考虑Python,它是一种纯粹的传递值语言。python变量将对象的位置存储在内存中。python变量不存储对象本身。将变量传递给函数时,将传递 复制 变量指向的对象的地址。

    禁止这两种功能

    def foo(x):
        x[0] = 5
    
    def goo(x):
        x = []
    

    现在,当你在壳里打字的时候

    >>> cow = [3,4,5]
    >>> foo(cow)
    >>> cow
    [5,4,5]
    

    把这个和goo比较一下。

    >>> cow = [3,4,5]
    >>> goo(cow)
    >>> goo
    [3,4,5]
    

    在第一种情况下,我们将cow地址的副本传递给foo,foo修改了驻留在那里的对象的状态。对象被修改。

    在第二种情况下,您将一份Cow地址的副本传递给Goo。然后顾继续修改那个副本。效果:无。

    我把这个叫做 粉红屋原则 . 如果你把你的地址复印一份然后告诉 画家把那个地址的房子漆成粉红色,你就会得到一个粉红色的房子。 如果你给油漆工一份地址副本,告诉他改成新地址, 你家的地址不变。

    这个解释消除了很多混乱。python传递按值存储的地址变量。

        9
  •  0
  •   sunxd    8 年前

    python是按引用值复制的。一个对象在内存中占有一个字段,并且一个引用与该对象相关联,但它本身在内存中占有一个字段。名称/值与引用关联。在python函数中,它总是复制引用的值,因此在代码中,n被复制为一个新名称,当您分配它时,它在调用方堆栈中有一个新的空间。但对于列表,名称也会被复制,但它指向相同的内存(因为您从未为列表分配新值)。这是巨蟒的魔法!

        10
  •  0
  •   Boris Epstein    6 年前

    我的一般理解是,任何对象变量(如列表或dict等)都可以通过其函数进行修改。我认为您不能做的是重新分配参数——即,在可调用函数中通过引用分配它。

    这与许多其他语言是一致的。

    运行以下简短脚本以查看其工作原理:

    
        def func1(x, l1):
        x = 5
        l1.append("nonsense")
    
    

    y = 10 list1 = ["meaning"] func1(y, list1) print(y) print(list1)

    `