美文网首页学习提升Python
【进阶Python】第十讲:可变对象与拷贝

【进阶Python】第十讲:可变对象与拷贝

作者: DevWeekly | 来源:发表于2020-01-18 13:23 被阅读0次

    公众号:【平凡而诗意】,我建了一个QQ学习交流群,旨在“分享、讨论、学习、资源分享、就业机会、互联网内推、共同进步!”,感兴趣的可以加一下,也可以添加我的QQ~ QQ群:1077239487;QQ号:498073774;

    首先,先推一波福利:

    自从加入这个群,我一个月省下10000块钱!

    前言

    学会一门编程语言的基础语法比较简单,但是精通一门编程语言却很难,就如同我在PyHubWeekly介绍的一个GitHub项目WTFPython总结的那样,在Python中有很多看似“合情合理”的用法,但是当实现之后发现却“事与愿违”,输出的结果和我们期望的大相径庭,首先来举一个例子,

    a = [1, 2, 3, 4]
    b = a
    print("[a] before append a value: {}".format(id(a)))
    print("[b] befaore append a value: {}".format(id(b)))
    
    a.append(5)
    print("[a] after append a value: {}".format(id(a)))
    print("[b] after append a value: {}".format(id(b)))
    print(b)
    
    # 输出
    [a] before append a value: 1948986356936
    [b] befaore append a value: 1948986356936
    [a] after append a value: 1948986356936
    [b] after append a value: 1948986356936
    [1, 2, 3, 4, 5]
    

    在上面例子中,我首先定义了一个变量a(list),把它赋值给变量b,分别输出它们的id。然后,往a中添加一个值,随后再次输出a、b的id和b的值。

    从上面例子中我们可以提取出下列信息:

    • 被赋值的变量、值发生改变后的变量id没有改变
    • 改变a的值,b的值也随之改变

    其中比较重要的就是第二点:改变a的值,b的值也随之改变,这就是可变对象的一个特性。如果我们在使用可变对象时,在其他函数中有操作赋值变量的部分,如果疏忽了很容易会造成错误。就如同上面的例子,“明明没有修改b的值,为什么输出的结果却不是[1, 2, 3, 4]?”

    这就引出了理解可变对象的重要性,下面就来详细介绍一下Python的可变对象与拷贝。

    可变对象

    可变对象中重点的概念就体现在“可变”这个词,可变对象的可变并不是体现在地址可变,而是体现在对象本身(值)的改变。换句话说,可变对象的特点在于当对象本身改变时它的地址不会改变。

    与“可变”相对的就是“不可变”,下面就列举一下Python内置可变对象和不可变对象,

    • 可变对象:list,set,dict
    • 不可变对象:int,float,bool,str,tuple,unicode

    就如同前面所说的那样,可变对象在修改它的值之后它的地址不会改变,但是不可变对象地址会被改变。下面来举个例子对比一下,

    可变对象

    >>> a = [1, 2, 3 ,5]
    >>> id(a)
    1955042932488
    >>> a[3] = 4
    >>> a
    [1, 2, 3, 4]
    >>> id(a)
    1955042932488
    

    不可变对象

    >>> a = 1
    >>> id(a)
    1706590320
    >>> a = 2
    >>> id(a)
    1706590352
    

    要想理解可变对象和不可变对象本质的区别,就要提及对象引用,我们可以把变量引用分解为两个部分:对象本身、对象引用(可以理解为指针)。

    对于不可变对象,当我们对不可变对象进行赋值或者改变对象本身时,它不会改变对象本身,而会改变对象应用,它会去新的地址创建一个新的对象。

    对于可变对象,恰恰相反,它不会改变对象引用,而会改变对象本身,会直接在原来地址上修改对象。

    可变对象应用

    介绍了Python中可变对象和不可变对象的概念和异同点,也许这只是如同一个概念一样存在脑子里,但是却不知道理解这个到底有什么价值?为什么要去学习这些?

    “实践出真知”,在编程语言中这一点同样适用,对于很多概念当我们从书本上看到时它只是一段解释、一段文字,却不知道该怎么去用它。

    对于Python可变对象,我来介绍一个在赋值方面的应用。

    定义变量、赋值是在开发过程中非常常用的操作,我们习以为常的去定义一个变量,然后把它赋值给其他不同的变量,然后传入不同的函数实现不同的功能,在潜意识中,会把“赋值”理解为“复制粘贴”,会把赋值理解为在原来变量的基础上拷贝一份传递给另外一个变量,但是,当我们输出是却发现并不像我们想象的那样,

    def print_dict(param, statement):
        print("Current variable is : {}".format(statement))
        for k, v in param.items():
            print("Key: {}, Value: {}".format(k, v))
    
    
    def main():
        info = {
            "age": 28,
            "country": "China"
        }
        copy_info = info
    
        print_dict(info, "info")
    
        info["age"] = 27
        print_dict(copy_info, "copy_info")
    
    # 输出
    Current variable is : info
    Key: age, Value: 28
    Key: country, Value: China
    Current variable is : copy_info
    Key: age, Value: 27
    Key: country, Value: China
    

    在上述例子中,首先定义了一个变量info,然后把它赋值给copy_info,当我们改变info中的值之后发现copy_info中的值也随之改变了。

    这是一个简单却又非常常见的例子,在开发过程中会经常遇到这样的场景:对同样的变量进行不同的操作,如果某个调用中对变量进行了“写”操作,那么在不知不觉中这个赋值的变量已经不是之前的变量了,如果疏忽了这一点,必然会造成输出结果错误。

    这样我们就要针对这个问题想一下解决方法,好在Python提供了内置模块copy,能够解决可变对象的上述问题,下面就来介绍一下copy这个模块。

    copy

    前面介绍了可变对象与不可对象的区别:不可变对象在赋值或者修改之后会重新申请地址,而不是在原来地址上修改对象。根据这一点,Python提供了内置copy模块,能够把变量进行拷贝到一个新的地址。

    copy模块中包括两种拷贝方式:

    • 浅拷贝
    • 深拷贝

    下面分别来介绍一下这两种拷贝方式。

    浅拷贝

    对于浅拷贝要根据要拷贝的对象而区分对待,如果它拷贝的时一个不可边对象,那么和赋值操作的作用是相同的,下面着重的介绍一下它在拷贝不可边对象时的特点。

    拷贝之所以称为“浅”是因为它只拷贝父对象,不会拷贝对象的内部的子对象。换句话说,如果外层是一个可变对象,内部在包含一个可变对象时,使用浅拷贝它只会拷贝最外层对象,举个例子,

    >>> from copy import copy
    >>> a = [1, 2, 3, [4, 5, 6]]
    >>> b = copy(a)
    >>> id(a)
    1955044381576
    >>> a[0] = 0
    >>> a
    [0, 2, 3, [4, 5, 6]]
    >>> b
    [1, 2, 3, [4, 5, 6]]
    >>> a[3][1] = 7
    >>> a
    [0, 2, 3, [4, 7, 6]]
    >>> b
    [1, 2, 3, [4, 7, 6]]
    

    在上面示例中,定义了一个变量a,它外层是一个list(可变对象),内层又包含了一个可变对象(list),当我们使用浅拷贝(copy.copy)后,改变外层对象的值,赋值变量不会再跟随着改变,但是我们改变内部子对象时,赋值的变量就会跟随着改变。

    深拷贝

    深拷贝和浅拷贝相对,它不仅拷贝父对象,也拷贝子对象,同样使用上面的例子来验证一下深拷贝的作用。

    >>> from copy import deepcopy
    >>> a = [1, 2, 3, [4, 5, 6]]
    >>> b = deepcopy(a)
    >>> a[3][1] = 7
    >>> a
    [1, 2, 3, [4, 7, 6]]
    >>> b
    [1, 2, 3, [4, 5, 6]]
    

    现在看一下,即便是修改子对象,赋值变量的值也不会跟随我们的改变。

    结语

    之所以在这里介绍Python可变对象,就是因为并不是所有的Python内置对象都像我们潜意识认知的那样,“赋值后就如同拷贝了一份数据,原始变量与赋值变量之间不会互相影响”。但是,事实上Python可变对象并不像我们想象的那样,原始变量与赋值变量之间会有互相作用,如果疏忽这一点会发生一些我们无法理解的错误,这类语言特性造成错误在调试过程中也很难定位,当我们了解这些容易被忽略的特性之后则不然,就能够有意识的避免Python特性带来的异常。

    相关文章

      网友评论

        本文标题:【进阶Python】第十讲:可变对象与拷贝

        本文链接:https://www.haomeiwen.com/subject/wgukzctx.html