zoukankan      html  css  js  c++  java
  • 【Python】可变、不可变对象和赋值技巧序列解包

    可变对象和不可变对象

    在python中一切皆对象。在Python中不存在所谓的值传递调用,一切传递都是对象的引用,也可认为是传址。

    python中,对象分为可变(mutable)和不可变(immutable)两种类型,元组(tuple)、数值型(number)、字符串(string)均为不可变对象,而字典型(dictionary)和列表型(list)的对象是可变对象。

    不可变对象

    见一个例子,分析不可变对象的特点

    python内置id()函数,用于返回对象的唯一标识(identity)。id()返回的是十进制,对象实际内存地址为hex(id(object)),本文中将id()与hex(id())等价使用。

    >>> a = 1 #将变量a与内存中的值为1的内存绑定在一起
    >>> a = 2 # 将变量a与内存中值为2的内存绑定在一起,并不是修改原来a绑定的内存中的值,
        # 这时,原来的这个值为1的内存地址的引用次数减一,当引用计数为0时,内存地址被回收
    >>> b = a # 变量b绑定与a一样的内存
    >>> id(b),id(a)  # 打印a,b的绑定的内存地址
    (1972461824, 1972461824)
    >>> b = 3 # 创建一个内存值为3的内存地址与变量名字b进行绑定。这时,a还是指向值为2的内存地址
    >>> a,b
    (2, 3)
    >>> id(b),id(a)  # 打印a,b的绑定的内存地址
    (1972461856, 1972461824)
    
    >>> x = 1
    >>> y = 1
    >>> z = 1
    >>> x is y
    True
    >>> y is z
    True
    >>> id(x),id(y),id(z)
    (1972461792, 1972461792, 1972461792)
    

    从第二个例子可看出因为整数为不可变对象,x,y,z在内存中均指向一个值为1的内存地址。
    不可变对象最大的优点便是减少重复的值对内存空间的占用

    缺点便是如第一个例子中所示,我要修改这个变量绑定的值,如果内存中没有存在该值的内存块,那么必须重新开辟一块内存,把新地址与变量名绑定。

    而不是修改变量原来指向的内存块的值,这回给执行效率带来一定的降低


    原来的内存块会因变量与其他内存块绑定而引用次数减1.

    下面是对第一个例子的图解

    可变对象

    继续看一个例子

    例1

    >>> a = [1]
    >>> b = a # a,b绑定同一内存地址,内存中值为[1]
    >>> id(a),id(b)
    (1991064334856, 1991064334856)
    >>> b.append(2)
    >>> a,b
    ([1, 2], [1, 2])
    

    变量名a和b是绑定的同一内存地址,对任一个变量对应的值的修改,都会反映到另一个变量上。也就是说对可变对象的操作,是直接对这个对象进行改变。


    例2

    >>> a =[1]
    >>> b=[1]
    >>> id(a),id(b)
    (1991065258248, 1991064760520)
    

    由此可见,在可变对象中值相同的变量绑定的不一定是相同的内存地址,会指向不同的对象

    例3

    在函数默认形参值中使用可变对象会出现一个大坑见例子:

    >>> def add_end(L=[]):
    ...     L.append('End')
    ...     return L
    ...
    >>> add_end([1,2])
    [1, 2, 'End']
    >>> add_end([a,b])
    [[1], [1], 'End']
    >>> add_end()
    ['End']
    >>> add_end()
    ['End', 'End']
    >>> add_end()
    ['End', 'End', 'End']
    

    这里当正常传参调用函数时一切正常,可是不断调用带默认形参是值得函数时,我们觉得的答案应该是['End'],但是仿佛次调用都记住了上次的值。

    这原因为何?

    这看起来有点像是每次调用的默认形参值变化了。我们用add_end.__defaults__来查看函数对象的默认参数变化情况

    >>> def add_end(L=[]):
    ...     L.append('End')
    ...     return L
    ...
    >>> add_end.__defaults__
    ([],)
    >>> add_end()
    ['End']
    >>> add_end.__defaults__
    (['End'],)
    >>> add_end()
    ['End', 'End']
    >>> add_end()
    ['End', 'End', 'End']
    >>> add_end.__defaults__
    (['End', 'End', 'End'],)
    

    由上可知每调用一次带默认形参值的函数,该函数对象的默认形参值就会发生变化,所以下次调用的默认形参值就是变化过后的。究其原因还是因为默认形参值为可变对象,导致每次调用会改变默认形参值

    所以,在编程时可尽量将对象设计为不变对象,可以避免一些麻烦。

    赋值操作技巧---序列解包(递归解包)

    将含多个值的序列解开,放到变量的序列中。解包序列中元素的数量必须和赋值符号=左边变量数量一致,否则会报错,参数太多或太少。

    >>> values = 1,2,3
    >>> values
    (1, 2, 3)
    >>> x,y,z = values
    >>> print(x,y,z)
    1 2 3
    >>> a,b,c = 1,2,3,4,5
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    ValueError: too many values to unpack (expected 3)
    >>> a,b,c=1,2
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    ValueError: not enough values to unpack (expected 3, got 2)
    

    当解包序列中的元素多多余变量数量时,变量序列中可以使用星号运算符*,对值进行收集

    >>> a,b,*c=1,2,3,4,5
    >>> print(a,b,c)
    1 2 [3, 4, 5]
    
  • 相关阅读:
    Spring的事务管理
    C#的WinForm中制作饼状图和柱状图
    .net+mssql制作抽奖程序思路及源码
    C#中简单调用MD5方法以及MD5简介
    【好文翻译】一步一步教你使用Spire.Doc转换Word文档格式
    C#调用C/C++动态库 封送结构体,结构体数组
    【好文翻译】测试必看:使用Spire.XLS来生成自动化报表!
    浅析C#基于TCP协议的SCOKET通信
    C# RSA和Java RSA互通
    C#创建windows服务搭配定时器Timer使用实例(用代码做,截图版)
  • 原文地址:https://www.cnblogs.com/myworld7/p/8449618.html
Copyright © 2011-2022 走看看