美文网首页流畅的python
可变对象、不可变对象、赋值、引用、拷贝、作用域

可变对象、不可变对象、赋值、引用、拷贝、作用域

作者: 洛克黄瓜 | 来源:发表于2018-11-25 21:19 被阅读0次

    refer:
    https://www.jianshu.com/p/c5582e23b26c
    https://my.oschina.net/leejun2005/blog/145911

    不可变对象

    • int, float, string, tuple
    j = 2333
    i = j
    k = 2333
    
    print id(i)
    print id(j)
    pring id(k)
    print i is j
    print j is k 
    
    print 'after j +1'
    j = j + 1
    print id(j)
    print i
    
    # output:
    140565451698264
    140565451698264
    140565451698264
    True
    True
    after j +1
    2333
    2334
    140565451698216
    
    • 不可变对象(2333)在内存地址(140565451698264)中存放后,该值就不可变了;一旦去改变(j = j + 1)实际上是重新分配了内存地址(140565451698216),新地址存的是所赋的值(j+1),j 再指向新的内存地址。
    • refer中的例子图很形象:


      image.png

    可变对象

    • list, dict, set
    a = dict()
    b = a
    a["k"] = "v"
    print "a id: {}".format(id(a))
    print "b id: {}".format(id(b))
    print b
    
    # output:
    a id: 4416436776
    b id: 4416436776
    {'k': 'v'}
    

    可变对象a的地址是4416436776,a赋值给b后,实际是a和b指向同一个内存地址。
    上述可见,改变a的值就是直接在该内存空间直接改变存储对象的值,所以b的内容也跟着变化了。
    refer中的图例:

    image.png

    函数的参数传递

    python里的规则是函数的参数传递都是传递引用,即传入参数的内存地址。表面看,函数内部对参数的改变会影响参数本身。
    但python有可变对象和不可变对象,结合上文可知,

    • 参数是可变对象类型的时候,函数如果对参数有变动会影响参数本身的值(“引用传递”),这个跟C的指针很像
    • 参数是不可变对象类型的时候,函数对参数的变动实际上是重新分配了内存空间的,所以参数本身值不受影响(值传递)
    def test(a_int, b_list):
        a_int = a_int + 1
        b_list.append('13')
        print('inner a_int:' + str(a_int))
        print('inner b_list:' + str(b_list))
    
    a_int = 5
    b_list = [10, 11]
    test(a_int, b_list)
    print('outer a_int:' + str(a_int))
    print('outer b_list:' + str(b_list))
    
    # output:
    inner a_int:6
    
    inner b_list:[10, 11, '13']
    
    outer a_int:5
    
    outer b_list:[10, 11, '13']
    

    示例讲解

    • 在 python 中赋值语句总是建立对象的引用值,而不是复制对象。因此,python 变量更像是指针,而不是数据存储区域,
    lst = [0,1,2]
    lst[1] = lst
    lst 
    
    # output:
    [0, [...], 2]
    

    可以说 Python 没有赋值,只有引用。你这样相当于创建了一个引用自身的结构,所以导致了无限循环

    image.png
    • 通过lst[:]形式来复制操作得到新对象
    lst = [0,1,2]
    lst[1] = lst[:]
    lst 
    
    # output:
    [0, [0,1,2], 2]
    

    生成对象的拷贝或者是复制序列,不再是引用和共享变量,但此法只能顶层复制


    image.png
    • 往更深处说,values[:] 复制操作是所谓的「浅复制」(shallow copy)
    a = [0, [1, 2], 3]
    b = a[:]
    a[0] = 8
    a[1][1] = 9
    
    a # [8, [1, 9], 3]
    b # [0, [1, 9], 3] 
    
    image.png
    • 通过copy.deepcopy来【深复制】(实际上应该是递归进行copy)
    import copy
    
    a = [0, [1, 2], 3]
    b = copy.deepcopy(a)
    a[0] = 8
    a[1][1] = 9
    
    a # [8, [1, 9], 3]
    b #  [0, [1, 2], 3]
    
    image.png
    • 字典 copy 方法,D.copy() 能够复制字典,但此法只能浅层复制

    对可变对象处理+=需要注意

    x = x + y,x 出现两次,必须执行两次,性能不好,合并必须新建对象 x,然后复制两个列表合并

    属于复制/拷贝

    x += y,x 只出现一次,也只会计算一次,性能好,不生成新对象,只在内存块末尾增加元素。

    当 x、y 为list时, += 会自动调用 extend 方法进行合并运算,in-place change。

    属于共享引用

    L = [1, 2]
    M = L
    L = L + [3, 4]
    print L, M
    print "-------------------"
    L = [1, 2]
    M = L
    L += [3, 4]
    print L, M
    
    
    [1, 2, 3, 4] [1, 2]
    -------------------
    [1, 2, 3, 4] [1, 2, 3, 4]
    

    陷阱:使用可变的默认参数

    In[2]: def foo(a, b, c=[]):
    ...        c.append(a)
    ...        c.append(b)
    ...        print(c)
    ...
    In[3]: foo(1, 1)
    [1, 1]
    In[4]: foo(1, 1)
    [1, 1, 1, 1]
    In[5]: foo(1, 1)
    [1, 1, 1, 1, 1, 1]
    

    同一个变量c在函数调用的每一次都被反复引用。这可能有一些意想不到的后果。

    相关文章

      网友评论

        本文标题:可变对象、不可变对象、赋值、引用、拷贝、作用域

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