python黑魔法之參數(shù)傳遞
我們都聽說,python世界里面,萬物皆對象。
怎么說萬物皆對象呢?最常見的:
> class A: pass > a = A()
我們說a是一個對象。
那么既然是萬物了,其實A也是對象。3 也是對象。True 也是對象。"hello" 也是對象。
> def Func(): pass
o~yee, Func 也是對象。
那么對象之間的傳遞是如何呢?我們看看下面兩個簡單的例子:
> 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的實現(xiàn)上,對象分為mutable 和 immutable。
這里說的對象分類,是說在實現(xiàn)上具備這樣的特性。而非對象本身的屬性。
什么是immutable?表示對象本身不可改變。這里先記住一點,是對象 本身 不可改變。
什么叫做對象本身不可改變呢?
一個簡單的例子:
> a = (1,2,3) > a[0] = 10
TypeError: 'tuple' object does not support item assignment
元組的元素在初始化后就不能再被改變。也就是說,元組對象具備immutable的特性。
那么很簡單,相對的,mutable 就是可變的。比如:
> a = {} > a[0] = 10
有了上面的兩個例子,相信大家已經(jīng)有了基本的認識。
那么,在python世界中,哪些是具備immutable特性,哪些又是mutable的呢?
簡單講,基本類型都是immutable, 而object都是mutable的。
比如說:int, float, bool, tuple 都是immutable。
再比如:dict, set, list, classinstance 都是mutable的。
那么問題來了。既然說基本類型是 immutable ,那么最上面的 b = 3 + 1 為什么不會像tuple一樣,拋異常呢?
原因在于,int 對+操作會執(zhí)行自己的__add__方法。而__add__方法會返回一個新的對象。
事實是,當(dāng)基本類型被改變時,并不是改變其自身,而是創(chuàng)建了一個新的對象。最終返回的是新的對象的引用。
怎么證明?
我們可以使用一個叫做id()的函數(shù)。該函數(shù)會返回對象的一個唯一id(目前的實現(xiàn)可以間接理解為對象的內(nèi)存地址)。
那么我們看下:
> a = 3 > id(a) 140248135804168 > id(3) 140248135804168 > id(4) 140248135804144 > a = a + 1 > id(a) 140248135804144
you see ? 當(dāng)我們執(zhí)行a=a+1 后,id(a) 已經(jīng)改變了。
深究一點,為什么會這樣呢?
其實,a = a + 1 經(jīng)歷了兩個過程:
- 1、a + 1
- 2、a 賦值
第2步只是一個引用的改變。重點在第1步。a + 1,那么python實際上會調(diào)用a.__add__(1)。
對于int類型__add__函數(shù)的實現(xiàn)邏輯,是創(chuàng)建了一個新的int對象,并返回。
不知道細心的你有沒有發(fā)現(xiàn)一個特別的地方?
id(4)的值等于id(3+1) 。這個只是python對int,和bool做的特殊優(yōu)化。不要以為其他基本類型只要值一樣都會指向相同的對象。
有個特殊的例子,str。做個簡單的實驗:
> a = "hello" > id(a) 4365413232 > b = "hell" > id(b) 4365386208 > id(a[:-1]) 4365410928 > id(a[:-1]) 4365413760
看到了嗎?雖然值相同,但是還是指向(創(chuàng)建)了不同的對象,尤其是最后兩句,哪怕執(zhí)行相同的操作,依然創(chuàng)建了不同的對象。
python這么傻,每次都創(chuàng)建新的對象?
no no no 他只是緩存了“一些”結(jié)果。我們可以再試試看:
> a = "hello" > ret = set() > for i in range(1000): ret.add(id(a[:-1])) > print ret {4388133312, 4388204640}
看到了嗎?python還是挺聰明的。不過具體的緩存機制我沒有深究過,期望有同學(xué)能分享下。
再次回到我們的主題,python中參數(shù)是如何傳遞的?
答案是,引用傳遞。
平時使用靜態(tài)語言的同學(xué)(比如我),可能會用下面的例子挑戰(zhàn)我了:
def fun(data): data = 3 a = 100 func(a) print a # 100
不是尼瑪引用傳遞嗎?為毛在執(zhí)行func(a)后,a 的值沒有改變呢?這里犯了一個動態(tài)語言基本的錯誤。
data=3,語義上是動態(tài)語言的賦值語句。千萬不要和C++之類的語言一個理解。
看看我們傳入一個mutable 的對象:
> def func(m): m[3] = 100 > a = {} > print a {} > func(a) > print a {3:100}
現(xiàn)在同學(xué)們知道該如何進行參數(shù)傳遞了吧?好嘞,進階!
像很多語言如C++,js,swift... 一樣,python 的函數(shù)聲明支持默認參數(shù):
def func(a=[]): pass
不知道什么意思?自己看書去!
我這里要說的是,如果我們的默認參數(shù)是mutable類型的對象,會有什么黑魔法產(chǎn)產(chǎn)生?
我們看看下面的函數(shù):
def func(a=[]): a.append(3) return a
可能有同學(xué)會說了:我去!這么簡單?來騙代碼的吧?
但是,真的這么簡單嗎?我們看下下面的調(diào)用結(jié)果:
> print func() [3] > print func() [3,3] > print func() [3,3,3]
這真的是你想要的結(jié)果嗎?
No,我要的是[3],[3],[3]!
原因?好吧,我們再用下id()神奇看看:
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中,*默認參數(shù)不是每次執(zhí)行時都創(chuàng)建的!*
這下你再想想,曾經(jīng)嘲笑過的代碼(至少我)為什么要 多此一舉:
def func(a=None): if a is None: a = []
這里在順帶提一下==, is:
== : 值比較
is : 比較左右兩邊是否是同一個對象。 a is b ==> id(a) == id(b)
ok, let's move on!
我們都知道,在python中,不定參數(shù)我們可以這樣定義:
def func(*args, **kv): pass
什么你不知道?看書去!
那args和kv到底是什么情況呢?到底是mutable 還是 immutable 呢?
再一次請出id()神器:
def func(*args): print id(args) > a = [1,2] > print id(a) 4364874816 > func(*a) 4364698832 > func(*a) 4364701496
看到了吧?實際上args也會產(chǎn)生一個新的對象。但是值是填入的傳入?yún)?shù)。那么每一個item也會復(fù)制嗎?
我們再看看:
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 只是限制自身的屬性能否被改變,而不會影響到其引用的對象。
看下下面的例子:
> 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)
最最最后,我有個對象,它本身應(yīng)該是 mutable 的,但是我想讓他具備類似immutable的特性,可以嗎?
答案是,可以模擬!
還是之前說的,immutable 限制的是其自身屬性不能改變。
那么,我們的可以通過重定義(重載)屬性改變函數(shù),來模擬immutable特性。
python可以嗎?O~Yee
在python的類函數(shù)中,有這樣的兩個函數(shù): __setattr__ 和 __delattr__。分別會在對象屬性賦值和刪除時執(zhí)行。
那么我們可以進行簡單重載來模擬immutable:
class A: def __setattr__(self, name, val): raise TypeError("immutable object could not set attr")
以上就是為大家介紹的python黑魔法,希望對大家的學(xué)習(xí)有所幫助。
- Python黑魔法遠程控制開機的實例
- Python黑魔法@property裝飾器的使用技巧解析
- Python黑魔法Descriptor描述符的實例解析
- python黑魔法之編碼轉(zhuǎn)換
- 詳解python metaclass(元類)
- python中metaclass原理與用法詳解
- Python探索之Metaclass初步了解
- 舉例講解Python中metaclass元類的創(chuàng)建與使用
- 詳解python單例模式與metaclass
- Python使用metaclass實現(xiàn)Singleton模式的方法
- Python中的Classes和Metaclasses詳解
- 深入理解Python中的元類(metaclass)
- Python黑魔法之metaclass詳情
相關(guān)文章
Python Matplotlib繪圖基礎(chǔ)知識代碼解析
這篇文章主要介紹了Python Matplotlib繪圖基礎(chǔ)知識代碼解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08ubuntu環(huán)境下python虛擬環(huán)境的安裝過程
這篇文章主要介紹了ubuntu環(huán)境下python虛擬環(huán)境的安裝搭建過程 ,需要的朋友可以參考下2018-01-01Python 中下劃線的幾種用法(_、_xx、xx_、__xx、__xx__)
本文主要介紹了Python 中下劃線的幾種用法(_、_xx、xx_、__xx、__xx__),詳細的介紹了這幾種下劃線的用處,具有一定的參考價值,感興趣的可以了解一下2023-09-09python中三種高階函數(shù)(map,reduce,filter)詳解
在Python中,函數(shù)其實也是一種數(shù)據(jù)類型,今天重點給大家介紹python中三種高階函數(shù)(map,reduce,filter)的相關(guān)知識,感興趣的朋友一起看看吧2021-10-10Python語法學(xué)習(xí)之正則表達式的量詞匯總
通過正則的規(guī)則匹配到的信息都是一個單獨的字符存到輸出結(jié)果中的,如何更夠根據(jù)字符串中的詞組進行匹配呢?因此本文將帶大家學(xué)習(xí)一下正則表達式中的量詞符號與組的概念,感興趣的可以了解一下2022-04-04