二维列表的深浅拷贝问题

本文探讨了Python中的浅拷贝问题,通过实例解释了为何使用['[]']*n创建的二维列表导致全为True。通过列表生成式解决了问题,并深入讲解了浅拷贝原理。同时介绍了递归和深拷贝的区别,以及如何避免浅拷贝带来的意外影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

问题来源

深浅拷贝的解释

    官方文档

      通俗解释

解决问题

拓展讨论


文末分享有几篇我觉得很有帮助的文章,如果你觉得这篇文章对你也有帮助的话

可以点个赞让更多可能有同样困扰的小伙伴看到它呀~

问题来源

        想要解决如下问题:按序遍历一串命题词的所有解释。False对应0,True对应1,按序遍历即要求输入2,则输出[[False, False], [False, True], [True, False], [True, True]]。

def genInterpretations(n):
    r=['0'*n]*(2**n)
    L=[[0]*n]*(2**n)                # [[]*n]式创建二维列表,浅拷贝
    for i in range(2**n):
        b=''
        j=i
        while i!=0:
            b = str(i%2)+b
            i = int(i/2)
        r[j]='0'*(n-len(b))+b
        for k in range(n):      # 下标k
            if r[j][k]=='0':
                L[j][k]=False
            else:
                L[j][k]=True
    print(L)

        getInterpretations(2)调用函数,发现输出结果为:[[True, True], [True, True], [True, True], [True, True]],竟然都为True。再试了几组,发现全为True。

        这是为什么呢?当时联系猜想到是深浅拷贝的原因,debug一步步查看变量变化,发现果然!给L[j][k]赋值,会给L中每个分量的第k-1个赋值,小实验代码如下。

L=[[0]*2]*(2**2)    
print(L)            # [[0, 0], [0, 0], [0, 0], [0, 0]]
L[0][0]=1
print(L)            # [[1, 0], [1, 0], [1, 0], [1, 0]]

浅拷贝的解释

    官方文档

        官方文档有解释如下:

Note also that the copies are shallow; nested structures are not copied. This often haunts new Python programmers; consider:

>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]

What has happened is that [[]] is a one-element list containing an empty list, so all three elements of [[]] * 3 are (pointers to) this single empty list. 

      通俗解释

        lists = [[]] * 3 这样的一行只是把[]这个列表的指针复制了三遍存进了lists列表,三个指针指向相同的内存地址,通过其中一个指针改变内存中所存数据,三个指针访问到的数据都被改变。这样只是复制指针,不复制内存地址的操作叫做浅拷贝(Shallow Copy)。

        python中的对象有三个要素:id(id() 函数:返回对象的唯一标识,可以类比成该对象在内存中的地址), name, value,我们做个小实验,输出id确认解释。

L=[[0]*2]*(2**2)    
print(L)            # [[0, 0], [0, 0], [0, 0], [0, 0]]
L[0][0]=1
print(L)            # [[1, 0], [1, 0], [1, 0], [1, 0]]
print(id(L[0]), id(L[1]))       # 2283586771392 2283586771392 L两个分量的指针相同

        形象化的图像解释如下:

解决问题

        由此可见,[[]*n]式创建的二维列表是浅拷贝,不能达到我们想要其实现的功能。需要先分别创建子列表,这时的子列表是有不同的内存地址的,再把子列表放入父列表。可以用列表生成式(List Comprehensions)法创建这样的二维列表。

def genInterpretations(n):
    r=['0'*n]*(2**n)
    L=[[0]*n for i in range((2**n))]        # 列表生成式创建二维空数组
    for i in range(2**n):
        b=''
        j=i
        while i!=0:
            b = str(i%2)+b
            i = int(i/2)
        r[j]='0'*(n-len(b))+b
        for k in range(n):      # 下标k
            if r[j][k]=='0':
                L[j][k]=False
            else:
                L[j][k]=True
    print(L)

        成功解决问题,输出想要的结果。 

拓展讨论

        注意直接用“=”赋值是一种浅拷贝方式,同时可以思考一下如何深拷贝一个二维列表(有父对象,也有子对象,那么分几个层次的“深拷贝”?该如何实现?现成的库函数是什么?)可参考文章:Python 直接赋值、浅拷贝和深度拷贝解析 | 菜鸟教程 (runoob.com)

        列表生成式方法:列表生成式 - 廖雪峰的官方网站 (liaoxuefeng.com)

        关于我这个问题来源还有更简单的递归做法……再次感叹列表生成式的强大……

        递归可参考文章Divide and Conquer Algorithms with Python Examples | Skerritt.blog。这个博主的其他文章也很不错👍

def genInterpretations(n):
    if n==0:
        return [[]]    # 递归终止条件
    else:
        return [sub+add for sub in genInterpretations(n-1) for add in [[False], [True]]]
        # 递归关系式
# 我原来做法是先放入排好序的二进制数,再对二进制数字符遍历输出对应的bool值……
# 其实可以直接绕过二进制数,对bool值进行处理
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值