9.3.4. 方法對象
創(chuàng)新互聯(lián)服務(wù)項目包括林周網(wǎng)站建設(shè)、林周網(wǎng)站制作、林周網(wǎng)頁制作以及林周網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,林周網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到林周省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
通常,方法通過右綁定方式調(diào)用:
x.f()
在 MyClass 示例中,這會返回字符串 'hello world'。然而,也不是一定要直接調(diào)用方法。 x.f 是一個方法對象,它可以存儲起來以后調(diào)用。例如:
xf = x.f
while True:
print(xf())
會不斷的打印 hello world。
調(diào)用方法時發(fā)生了什么?你可能注意到調(diào)用 x.f() 時沒有引用前面標(biāo)出的變量,盡管在 f() 的函數(shù)定義中指明了一個參數(shù)。這個參數(shù)怎么了?事實上如果函數(shù)調(diào)用中缺少參數(shù),Python 會拋出異常--甚至這個參數(shù)實際上沒什么用……
實際上,你可能已經(jīng)猜到了答案:方法的特別之處在于實例對象作為函數(shù)的第一個參數(shù)傳給了函數(shù)。在我們的例子中,調(diào)用 x.f() 相當(dāng)于 MyClass.f(x) 。通常,以 n 個參數(shù)的列表去調(diào)用一個方法就相當(dāng)于將方法的對象插入到參數(shù)列表的最前面后,以這個列表去調(diào)用相應(yīng)的函數(shù)。
如果你還是不理解方法的工作原理,了解一下它的實現(xiàn)也許有幫助。引用非數(shù)據(jù)屬性的實例屬性時,會搜索它的類。如果這個命名確認為一個有效的函數(shù)對象類屬性,就會將實例對象和函數(shù)對象封裝進一個抽象對象:這就是方法對象。以一個參數(shù)列表調(diào)用方法對象時,它被重新拆封,用實例對象和原始的參數(shù)列表構(gòu)造一個新的參數(shù)列表,然后函數(shù)對象調(diào)用這個新的參數(shù)列表。
眾所周知,Python是一門面向?qū)ο蟮恼Z言,在Python無論是數(shù)值、字符串、函數(shù)亦或是類型、類,都是對象。
對象是在 堆 上分配的結(jié)構(gòu),我們定義的所有變量、函數(shù)等,都存儲于堆內(nèi)存,而變量名、函數(shù)名則是一個存儲于 棧 中、指向堆中具體結(jié)構(gòu)的引用。
要想深入學(xué)習(xí)Python,首先需要知道Python對象的定義。
我們通常說的Python都是指CPython,底層由C語言實現(xiàn),源碼地址: cpython [GitHub]
Python對象的定義位于 Include/object.h ,是一個名為 PyObject 的結(jié)構(gòu)體:
Python中的所有對象都繼承自PyObejct,PyObject包含一個用于垃圾回收的雙向鏈表,一個引用計數(shù)變量 ob_refcnt 和 一個類型對象指針 ob_type
從PyObejct的注釋中,我們可以看到這樣一句:每個指向 可變大小Python對象 的指針也可以轉(zhuǎn)換為 PyVarObject* (可變大小的Python對象會在下文中解釋)。 PyVarObejct 就是在PyObject的基礎(chǔ)上多了一個 ob_size 字段,用于存儲元素個數(shù):
在PyObject結(jié)構(gòu)中,還有一個類型對象指針 ob_type ,用于表示Python對象是什么類型,定義Python對象類型的是一個 PyTypeObject 接口體
實際定義是位于 Include/cpython/object.h 的 _typeobject :
在這個類型對象中,不僅包含了對象的類型,還包含了如分配內(nèi)存大小、對象標(biāo)準(zhǔn)操作等信息,主要分為:
以Python中的 int類型 為例,int類型對象的定義如下:
從PyObject的定義中我們知道,每個對象的 ob_type 都要指向一個具體的類型對象,比如一個數(shù)值型對象 100 ,它的ob_type會指向 int類型對象PyLong_Type 。
PyTypeObject結(jié)構(gòu)體第一行是一個PyObject_VAR_HEAD宏,查看宏定義可知PyTypeObject是一個變長對象
也就是說,歸根結(jié)底 類型對象也是一個對象 ,也有ob_type屬性,那 PyLong_Type 的 ob_type 是什么呢?
回到PyLong_Type的定義,第一行 PyVarObject_HEAD_INIT(PyType_Type, 0) ,查看對應(yīng)的宏定義
由以上關(guān)系可以知道, PyVarObject_HEAD_INIT(PyType_Type, 0) = { { _PyObject_EXTRA_INIT 1, PyType_Type } 0} ,將其代入 PyObject_VAR_HEAD ,得到一個變長對象:
這樣看就很明確了,PyLong_Type的類型就是PyType_Typ,同理可知, Python類型對象的類型就是PyType_Type ,而 PyType_Type對象的類型是它本身
從上述內(nèi)容中,我們知道了對象和對象類型的定義,那么根據(jù)定義,對象可以有以下兩種分類
Python對象定義有 PyObject 和 PyVarObject ,因此,根據(jù)對象大小是否可變的區(qū)別,Python對象可以劃分為 可變對象(變長對象) 和 不可變對象(定長對象)
原本的對象a大小并沒有改變,只是s引用的對象改變了。這里的對象a、對象b就是定長對象
可以看到,變量l仍然指向?qū)ο骯,只是對象a的內(nèi)容發(fā)生了改變,數(shù)據(jù)量變大了。這里的對象a就是變長對象
由于存在以上特性,所以使用這兩種對象還會帶來一種區(qū)別:
聲明 s2 = s ,修改s的值: s = 'new string' ,s2的值不會一起改變,因為只是s指向了一個新的對象,s2指向的舊對象的值并沒有發(fā)生改變
聲明 l2 = l ,修改l的值: l.append(6) ,此時l2的值會一起改變,因為l和l2指向的是同一個對象,而該對象的內(nèi)容被l修改了
此外,對于 字符串 對象,Python還有一套內(nèi)存復(fù)用機制,如果兩個字符串變量值相同,那它們將共用同一個對象:
對于 數(shù)值型 對象,Python會默認創(chuàng)建0~2 8 以內(nèi)的整數(shù)對象,也就是 0 ~ 256 之間的數(shù)值對象是共用的:
按照Python數(shù)據(jù)類型,對象可分為以下幾類:
Python創(chuàng)建對象有兩種方式,泛型API和和類型相關(guān)的API
這類API通常以 PyObject_xxx 的形式命名,可以應(yīng)用在任意Python對象上,如:
使用 PyObjecg_New 創(chuàng)建一個數(shù)值型對象:
這類API通常只能作用于一種類型的對象上,如:
使用 PyLong_FromLong 創(chuàng)建一個數(shù)值型對象:
在我們使用Python聲明變量的時候,并不需要為變量指派類型,在給變量賦值的時候,可以賦值任意類型數(shù)據(jù),如:
從Python對象的定義我們已經(jīng)可以知曉造成這個特點的原因了,Python創(chuàng)建對象時,會分配內(nèi)存進行初始化,然后Python內(nèi)部通過 PyObject* 變量來維護這個對象,所以在Python內(nèi)部各函數(shù)直接傳遞的都是一種泛型指針 PyObject* ,這個指針?biāo)赶虻膶ο箢愋褪遣还潭ǖ?,只能通過所指對象的 ob_type 屬性動態(tài)進行判斷,而Python正是通過 ob_type 實現(xiàn)了多態(tài)機制
Python在管理維護對象時,通過引用計數(shù)來判斷內(nèi)存中的對象是否需要被銷毀,Python中所有事物都是對象,所有對象都有引用計數(shù) ob_refcnt 。
當(dāng)一個對象的引用計數(shù)減少到0之后,Python將會釋放該對象所占用的內(nèi)存和系統(tǒng)資源。
但這并不意味著最終一定會釋放內(nèi)存空間,因為頻繁申請釋放內(nèi)存會大大降低Python的執(zhí)行效率,因此Python中采用了內(nèi)存對象池的技術(shù),是的對象釋放的空間會還給內(nèi)存池,而不是直接釋放,后續(xù)需要申請空間時,優(yōu)先從內(nèi)存對象池中獲取。
python中函數(shù)和方法的區(qū)別:
首先,從分類的角度來分析。
(1)函數(shù)的分類:
內(nèi)置函數(shù):python內(nèi)嵌的一些函數(shù)。
匿名函數(shù):一行代碼實現(xiàn)一個函數(shù)功能。
遞歸函數(shù)
自定義函數(shù):根據(jù)自己的需求,來進行定義函數(shù)。
(2)方法的分類:
普通方法:直接用self調(diào)用的方法。
私有方法:__函數(shù)名,只能在類中被調(diào)用的方法。
屬性方法:@property,將方法偽裝成為屬性,讓代碼看起來更合理。
特殊方法(雙下劃線方法):以__init__為例,是用來封裝實例化對象的屬性,只要是實例化對象就一定會執(zhí)行__init方法,如果對象子類中沒有則會尋找父類(超類),如果父類(超類)也沒有,則直接繼承object(python 3.x)類,執(zhí)行類中的__init__方法。
類方法:通過類名的調(diào)用去操作公共模板中的屬性和方法。
靜態(tài)方法:不用傳入類空間、對象的方法, 作用是保證代碼的一致性,規(guī)范性,可以完全獨立類外的一個方法,但是為了代碼的一致性統(tǒng)一的放到某個模塊(py文件)中。
其次,從作用域的角度來分析:
(1)函數(shù)作用域:從函數(shù)調(diào)用開始至函數(shù)執(zhí)行完成,返回給調(diào)用者后,在執(zhí)行過程中開辟的空間會自動釋放,也就是說函數(shù)執(zhí)行完成后,函數(shù)體內(nèi)部通過賦值等方式修改變量的值不會保留,會隨著返回給調(diào)用者后,開辟的空間會自動釋放。
(2)方法作用域:通過實例化的對象進行方法的調(diào)用,調(diào)用后開辟的空間不會釋放,也就是說調(diào)用方法中對變量的修改值會一直保留。
最后,調(diào)用的方式不同。
(1)函數(shù):通過“函數(shù)名()”的方式進行調(diào)用。
(2)方法:通過“對象.方法名”的方式進行調(diào)用。
class Foo(object): ? ?def func(self): ? ? ? ?pass#實例化obj = Foo()# 執(zhí)行方式一:調(diào)用的func是方法obj.func() #func 方法# 執(zhí)行方式二:調(diào)用的func是函數(shù)Foo.func(123) # 函數(shù)
一、實例方法,類方法,靜態(tài)方法
我們首先寫一個類,里面包含這三種方法。
可以看到,我們用到了兩個裝飾器。
我們用類和實例分別調(diào)用下類方法
我們用類和實例分別調(diào)用下靜態(tài)方法
靜態(tài)方法其實就是把一個普通的函數(shù)寫在類里,與直接在外層寫一個函數(shù)是一樣的,本質(zhì)上是一個函數(shù)。
為了方便理解,我們分別打印下這些方法的類型
通過type()查看對象是方法還是函數(shù)
此外,還可以通過inspect模塊判斷某個對象是否是某種類型,返回布爾值。
用法
小Tips:概念理解
直接def定義的,我們叫做函數(shù)
把函數(shù)放到類里,我們叫做方法
方法可以通過裝飾器staticmethod轉(zhuǎn)為(放在方法里的)函數(shù)
繼承
一個類繼承另一個類時,會自動獲得另一個類的所有屬性和方法,被繼承的類稱之為父類,新類稱為子類。子類擁有父類所有的屬性和方法,并且可以定義自己的屬性和方法
我們以上邊的Rectangle類為父類來試一下
1)完全繼承
可以看到,子類完全繼承父類后,可以直接調(diào)用父類的所有方法。
2)部分繼承
部分繼承:繼承父類后,修改父類的同名方法
我們試一下,Square繼承Rectangle后,修改__init__()方法
3)拓展父類的方法
在保留父類中某個方法的代碼同時,對方法進行拓展
可以在方法中加入"super().方法名"來實現(xiàn)
4)@property
在Python中,對這兩個東西有明確的規(guī)定:
函數(shù)function —— A series of statements which returns some value to a caller. It can also be passed zero or more arguments which may be used in the execution of the body.
方法method —— A function which is defined inside a class body. If called as an attribute of an instance of that class, the method will get the instance object as its first argument (which is usually called self).
從定義的角度上看,我們知道函數(shù)(function)就相當(dāng)于一個數(shù)學(xué)公式,它理論上不與其它東西關(guān)系,它只需要相關(guān)的參數(shù)就可以。所以普通的在module中定義的稱謂函數(shù)是很有道理的。
那么方法的意思就很明確了,它是與某個對象相互關(guān)聯(lián)的,也就是說它的實現(xiàn)與某個對象有關(guān)聯(lián)關(guān)系。這就是方法。雖然它的定義方式和函數(shù)是一樣的。也就是說,在Class定義的函數(shù)就是方法。
從上面的角度看似乎很有道理。
def fun():
pass
type(fun)
class 'function' #沒有問題
class Cla():
def fun():
pass
@classmethod
def fun1(cls):
pass
@staticmethod
def fun2():
pass
i=Cla()
Cla.fun.__class__
class 'function' #為什么還是函數(shù)
i.fun.__class__ #這個還像話
class 'method'
type(Cla.fun1)
class 'method' #這里又是方法
type(i.fun1)
class 'method'#這里仍然是方法
type(Cla.fun2)
class 'function'?。_@里卻是函數(shù)
type(i.fun2)
class 'function'#這里卻是函數(shù)
事實上,上面的結(jié)果是可以解釋的:
1,普通方法(老版中直接就是"instancemethod")在module中與在Class中定義的普通函數(shù),從其本身而言是沒有什么區(qū)別的,他們都是對象函數(shù)屬性。 之所以會被說在Class中的定義的函數(shù)被稱為方法,是因為它本來就是面向?qū)淼膶嵗龑ο蟮?,其實他們就是實例方法,這些方法是與實例相聯(lián)系的(從實例出發(fā)訪問該函數(shù)會自動賦值)。所以你從Class訪問仍然是一個函數(shù)
2,類方法("classmethod"),因為類同樣是對象,所以如果函數(shù)與類進行聯(lián)系了話(與實例方法一樣的模式)那么就能夠這么說了!
3,靜態(tài)方法,雖然定義在內(nèi)部,并且也較方法,但是卻不與任何對象聯(lián)系,與從類訪問方法是一樣的,他們?nèi)匀皇呛瘮?shù)。
這樣看來上面的定義可以改改了:
函數(shù)的定義自然不變。
方法的定義可以是這樣的,與某個對象進行綁定使用的函數(shù)。注意哦。綁定不是指" . "這個符號,這個符號說實在的只有域名的作用。綁定在這里是指,會默認賦值該綁定的對象。
類,簡單來說就是一個函數(shù)的集合,在這個集合里面你定義了很多個函數(shù);方法,其實就是你定義的這些函數(shù)。在下面的例子中Class Plus就是一個類,嵌套在這個類里面的兩個函數(shù)就是所謂的方法,但是__init__只是用于初始化這個類,所以不算是方法。而get_result這個函數(shù)就是一個方法了。
舉個例子:
Class Plus:
def __init__(self, a,b)
self.a = a
self.b = b
def get_result(self)
return self.a + self.b
在上面這個例子中,self就是對象,這個對象里面有兩個參數(shù),一個是self.a,另一個是self.b,對象簡單來說就是自身具有多個屬性(也可以說是子變量)的變量而已。如果說對象是籠統(tǒng)的東西,那么實例就是具體的東西。對象只是一個模板,有一些屬性,而實例就是把數(shù)據(jù)填到這個模板里面去。比如這里你再這樣寫:c1 = Plus(1,2),這里c1就是一個實例,你還可以加一句:c2 = Plus(2,3),那c2也是一個實例,但是他們擁有共同的屬性和模板。下面的例子就是調(diào)用了類里面的方法:
result1 = c1.get_result() 3 輸出結(jié)果為3
result2 = c2.get_result() 5 輸出結(jié)果為5