欢迎访问Python教程网,我们的网址www.041b.com
为什么python更改列表 ‘y’ 也会更改列表 ‘x’? – python教程 - python编程学习交流
为什么python更改列表 ‘y’ 也会更改列表 ‘x’? – python教程 - python编程学习交流

为什么python更改列表 ‘y’ 也会更改列表 ‘x’?

admin2019-12-18 发布 python问答

如果你编写的代码就像下面一样:

>>>

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

你可能想知道为什么追加一个元素也改变了x。

产生这种结果有两个因素:

  1. 变量只是指向具体对象的名称。 执行 y = x 并不会为列表创建一个副本 —— 它只是创建了一个新变量 y 指向 x 所指向的同一对象。 这意味着只存在一个对象(列表),x 和 y 都是对它的引用。
  2. 列表属于 mutable 对象,这意味着你可以改变它的内容。

在调用 append() 之后,这个可变对象的内容由 [] 变为 [10]。 由于两个变量都指向同一对象,因此使用任何一个名称所访问到的都是修改后的值 [10]。

如果我们改为将不可变对象赋值给 x:

>>>

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

我们可以看到在此情况下 x 和 y 就不再相等了。 这是因为整数是 immutable 对象,当我们执行 x = x + 1 时我们并不是改变了 5 这个对象的值;而是创建了一个新的对象 (整数 6) 并将其赋值给 x (也就是改变了 x 所指向的对象)。 在赋值之后我们就有了两个对象 (整数 6 和 5) 以及分别指向它们的两个变量 (x 现在指向 6 而 y 仍然指向 5)。

某些操作 (例如 y.append(10) 和 y.sort()) 是改变原对象,而看上去相似的另一些操作 (例如 y = y + [10] 和 sorted(y)) 则是创建新对象。 通常在 Python 中 (以及在标准库的所有代码中) 会改变原对象的方法将返回 None 以帮助避免混淆这两种不同类型的操作。 因此如果你错误地使用了 y.sort() 并期望它将返回一个经过排序的 y 的副本,你得到的结果将会是 None,这将导致你的程序产生一个容易诊断的错误。

但是,还存在一类操作,不同的类型执行相同的操作会有不同的行为:那就是增强赋值运算符。 例如,+= 会原地改变列表,但不会改变元组或整数 (a_list += [1, 2, 3] 与 a_list.extend([1, 2, 3]) 一样都会改变 a_list,而 some_tuple += (1, 2, 3) 和 some_int += 1 则会创建新的对象)。

换而言之:

  • 如果我们有一个可变对象 (list, dict, set 等等),我们可以使用某些特定的操作来改变它,所有指向它的变量都会显示它的改变。
  • 如果我们有一个不可变对象 (str, int, tuple 等等),所有指向它的变量都将显示相同样的值,但凡是会改变这个值的操作将总是返回一个新对象。

如果你想知道两个变量是否指向相同的对象,你可以使用 is 运算符,或内置函数 id()。

如何编写带输出参数的函数(通过引用调用)?

请记住在 Python 中参数是通过赋值来传递的。 由于赋值只是创建了对象的引用,因此在调用者和被调用者的参数名称之间没有别名,所以本身是没有按引用调用的。 你可以通过多种方式实现所需的效果。

  1. 通过返回一个结果元组:
    def func2(a, b):
        a = 'new-value'        # a and b are local names
        b = b + 1              # assigned to new objects
        return a, b            # return new values
    
    x, y = 'old-value', 99
    x, y = func2(x, y)
    print(x, y)                # output: new-value 100
    

    这几乎总是最清晰明了的解决方案。

  2. 通过使用全局变量。 这种方式不是线程安全的,而且也不受推荐。
  3. 通过传递一个可变 (即可原地修改的) 对象:
    def func1(a):
        a[0] = 'new-value'     # 'a' references a mutable list
        a[1] = a[1] + 1        # changes a shared object
    
    args = ['old-value', 99]
    func1(args)
    print(args[0], args[1])    # output: new-value 100
    
  4. 通过传递一个会被改变的字典:
    def func3(args):
        args['a'] = 'new-value'     # args is a mutable dictionary
        args['b'] = args['b'] + 1   # change it in-place
    
    args = {'a': 'old-value', 'b': 99}
    func3(args)
    print(args['a'], args['b'])
    
  5. 或者在一个类实例中捆绑值:
    class callByRef:
        def __init__(self, /, **args):
            for key, value in args.items():
                setattr(self, key, value)
    
    def func4(args):
        args.a = 'new-value'        # args is a mutable callByRef
        args.b = args.b + 1         # change object in-place
    
    args = callByRef(a='old-value', b=99)
    func4(args)
    print(args.a, args.b)
    

    几乎没有任何适当理由将问题如此复杂化。

你的最佳选择是返回一个包含多个结果的元组。

转载原创文章请注明,转载自: python教程 - 为什么python更改列表 ‘y’ 也会更改列表 ‘x’? (https://www.041b.com/162.html)

留言

评论(1)

近期评论