1. 程式人生 > >Python中的可變、不可變對象和賦值技巧序列解包

Python中的可變、不可變對象和賦值技巧序列解包

lis 我們 最大 pac 唯一標識 src 技術分享 efault pre

可變對象和不可變對象

在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]

Python中的可變、不可變對象和賦值技巧序列解包