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]
    
  • 相关阅读:
    HDU 3951 (博弈) Coin Game
    HDU 3863 (博弈) No Gambling
    HDU 3544 (不平等博弈) Alice's Game
    POJ 3225 (线段树 区间更新) Help with Intervals
    POJ 2528 (线段树 离散化) Mayor's posters
    POJ 3468 (线段树 区间增减) A Simple Problem with Integers
    HDU 1698 (线段树 区间更新) Just a Hook
    POJ (线段树) Who Gets the Most Candies?
    POJ 2828 (线段树 单点更新) Buy Tickets
    HDU 2795 (线段树 单点更新) Billboard
  • 原文地址:https://www.cnblogs.com/myworld7/p/8449618.html
Copyright © 2011-2022 走看看