我們都聽說,python世界里面,萬物皆對象。
怎么說萬物皆對象呢?最常見的:
1
2
|
> class A: pass > a = A() |
我們說a是一個對象。
那么既然是萬物了,其實A也是對象。3 也是對象。True 也是對象。"hello" 也是對象。
> def Func(): pass
o~yee, Func 也是對象。
那么對象之間的傳遞是如何呢?我們看看下面兩個簡單的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
> a = 3 > b = a > b = 3 + 1 > print b 4 > print a 3 > a = [] > b = a > b.append( 1 ) > print a [ 1 ] > print b [ 1 ] |
不是都說python所有對象都是引用傳遞嗎?為毛第一個b不是3?
好吧。事實是,在python的實現上,對象分為mutable 和 immutable。
這里說的對象分類,是說在實現上具備這樣的特性。而非對象本身的屬性。
什么是immutable?表示對象本身不可改變。這里先記住一點,是對象 本身 不可改變。
什么叫做對象本身不可改變呢?
一個簡單的例子:
1
2
|
> a = ( 1 , 2 , 3 ) > a[ 0 ] = 10 |
TypeError: 'tuple' object does not support item assignment
元組的元素在初始化后就不能再被改變。也就是說,元組對象具備immutable的特性。
那么很簡單,相對的,mutable 就是可變的。比如:
1
2
|
> a = {} > a[ 0 ] = 10 |
有了上面的兩個例子,相信大家已經有了基本的認識。
那么,在python世界中,哪些是具備immutable特性,哪些又是mutable的呢?
簡單講,基本類型都是immutable, 而object都是mutable的。
比如說:int, float, bool, tuple 都是immutable。
再比如:dict, set, list, classinstance 都是mutable的。
那么問題來了。既然說基本類型是 immutable ,那么最上面的 b = 3 + 1 為什么不會像tuple一樣,拋異常呢?
原因在于,int 對+操作會執行自己的__add__方法。而__add__方法會返回一個新的對象。
事實是,當基本類型被改變時,并不是改變其自身,而是創建了一個新的對象。最終返回的是新的對象的引用。
怎么證明?
我們可以使用一個叫做id()的函數。該函數會返回對象的一個唯一id(目前的實現可以間接理解為對象的內存地址)。
那么我們看下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
> a = 3 > id (a) 140248135804168 > id ( 3 ) 140248135804168 > id ( 4 ) 140248135804144 > a = a + 1 > id (a) 140248135804144 |
you see ? 當我們執行a=a+1 后,id(a) 已經改變了。
深究一點,為什么會這樣呢?
其實,a = a + 1 經歷了兩個過程:
- 1、a + 1
- 2、a 賦值
第2步只是一個引用的改變。重點在第1步。a + 1,那么python實際上會調用a.__add__(1)。
對于int類型__add__函數的實現邏輯,是創建了一個新的int對象,并返回。
不知道細心的你有沒有發現一個特別的地方?
id(4)的值等于id(3+1) 。這個只是python對int,和bool做的特殊優化。不要以為其他基本類型只要值一樣都會指向相同的對象。
有個特殊的例子,str。做個簡單的實驗:
1
2
3
4
5
6
7
8
9
10
11
|
> a = "hello" > id (a) 4365413232 > b = "hell" > id (b) 4365386208 > id (a[: - 1 ]) 4365410928 > id (a[: - 1 ]) 4365413760 |
看到了嗎?雖然值相同,但是還是指向(創建)了不同的對象,尤其是最后兩句,哪怕執行相同的操作,依然創建了不同的對象。
python這么傻,每次都創建新的對象?
no no no 他只是緩存了“一些”結果。我們可以再試試看:
1
2
3
4
5
6
|
> a = "hello" > ret = set () > for i in range ( 1000 ): ret.add( id (a[: - 1 ])) > print ret { 4388133312 , 4388204640 } |
看到了嗎?python還是挺聰明的。不過具體的緩存機制我沒有深究過,期望有同學能分享下。
再次回到我們的主題,python中參數是如何傳遞的?
答案是,引用傳遞。
平時使用靜態語言的同學(比如我),可能會用下面的例子挑戰我了:
1
2
3
4
5
6
7
|
def fun(data): data = 3 a = 100 func(a) print a # 100 |
不是尼瑪引用傳遞嗎?為毛在執行func(a)后,a 的值沒有改變呢?這里犯了一個動態語言基本的錯誤。
data=3,語義上是動態語言的賦值語句。千萬不要和C++之類的語言一個理解。
看看我們傳入一個mutable 的對象:
1
2
3
4
5
6
7
8
9
|
> def func(m): m[ 3 ] = 100 > a = {} > print a {} > func(a) > print a { 3 : 100 } |
現在同學們知道該如何進行參數傳遞了吧?好嘞,進階!
像很多語言如C++,js,swift... 一樣,python 的函數聲明支持默認參數:
def func(a=[]): pass
不知道什么意思?自己看書去!
我這里要說的是,如果我們的默認參數是mutable類型的對象,會有什么黑魔法產產生?
我們看看下面的函數:
1
2
3
|
def func(a = []): a.append( 3 ) return a |
可能有同學會說了:我去!這么簡單?來騙代碼的吧?
但是,真的這么簡單嗎?我們看下下面的調用結果:
1
2
3
4
5
6
|
> print func() [ 3 ] > print func() [ 3 , 3 ] > print func() [ 3 , 3 , 3 ] |
這真的是你想要的結果嗎?
No,我要的是[3],[3],[3]!
原因?好吧,我們再用下id()神奇看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def func(a = []): print id (a) a.append( 3 ) return a > print func() 4365426272 [ 3 ] > print func() 4365426272 [ 3 , 3 ] > print func() 4365426272 [ 3 , 3 , 3 ] |
明白沒?原來在python中,*默認參數不是每次執行時都創建的!*
這下你再想想,曾經嘲笑過的代碼(至少我)為什么要 多此一舉:
1
2
3
|
def func(a = None ): if a is None : a = [] |
這里在順帶提一下==, is:
== : 值比較
is : 比較左右兩邊是否是同一個對象。 a is b ==> id(a) == id(b)
ok, let's move on!
我們都知道,在python中,不定參數我們可以這樣定義:
def func(*args, **kv): pass
什么你不知道?看書去!
那args和kv到底是什么情況呢?到底是mutable 還是 immutable 呢?
再一次請出id()神器:
1
2
3
4
5
6
7
8
9
10
11
|
def func( * args): print id (args) > a = [ 1 , 2 ] > print id (a) 4364874816 > func( * a) 4364698832 > func( * a) 4364701496 |
看到了吧?實際上args也會產生一個新的對象。但是值是填入的傳入參數。那么每一個item也會復制嗎?
我們再看看:
1
2
3
4
5
6
7
8
|
def func( * args): print id (args[ 0 ]) > a = [ 1 , 2 ] > print id (a[ 0 ]) 140248135804216 > func( * a) 140248135804216 |
答案是,No。值會像普通list賦值一樣,指向原先list(a)所引用的對象。
那么為什么會這樣呢?
python的源碼就是這么寫的.......
最最后,還記得我說過的一句話嗎?
immutable 限制的是對象本身不可變
意思就是說,對象的immtable 只是限制自身的屬性能否被改變,而不會影響到其引用的對象。
看下下面的例子:
1
2
3
4
5
6
7
8
9
10
|
> a = [ 1 , 2 ] > b = (a, 3 ) > b[ 1 ] = 100 TypeError: 'tuple' object does not support item assignment > print b ([ 1 , 2 ], 3 ) > b[ 0 ][ 0 ] = 10 > print b ([ 10 , 2 ], 3 ) |
最最最后,我有個對象,它本身應該是 mutable 的,但是我想讓他具備類似immutable的特性,可以嗎?
答案是,可以模擬!
還是之前說的,immutable 限制的是其自身屬性不能改變。
那么,我們的可以通過重定義(重載)屬性改變函數,來模擬immutable特性。
python可以嗎?O~Yee
在python的類函數中,有這樣的兩個函數: __setattr__ 和 __delattr__。分別會在對象屬性賦值和刪除時執行。
那么我們可以進行簡單重載來模擬immutable:
1
2
3
|
class A: def __setattr__( self , name, val): raise TypeError( "immutable object could not set attr" ) |
以上就是為大家介紹的python黑魔法,希望對大家的學習有所幫助。