摘要:實例變量與類變量事實上,字段除了獨屬于實例之外,跟普通變量沒有什么差別,所以實例的字段也被稱為實例變量。在類的定義中,與實例變量對應的還有類變量,類變量與實例變量類似,通過操作符來訪問。類變量跟類的方法都可以被稱為類的成員。
該系列文章:
《python入門,編程基礎概念介紹(變量,條件,函數,循環)》
《python中的數據類型(list,tuple,dict,set,None)》
《在python中創建對象(object)》
在上一篇文章《python中的數據類型(list,tuple,dict,set,None)》的1.2小節里我們就簡要介紹過對象(object)跟類(class)的概念。也知道了python中內置的所有數據類型都是對象,擁有自己的方法。那么當這些內置的數據類型無法滿足我們的需求時,我們如何創建我們自己的類型(type)呢?答案就是通過創建我們自己的類(class)。通過我們自己動手實現的類,我們就可以創建以這個類為模板的對象。從這樣的流程來看,面向對象的編程方式是自頂而下,首先需要全盤考慮,才能創建一個足夠好的模板,也即類。然后才能將類實例化為對象,通過對象中的屬性來解決問題或者與其他對象互動。
創建一個最簡單的類可以通過下面這樣的寫法:
class User: pass #更多代碼 #更多代碼
上面的代碼中class是關鍵字,表明我們要創建一個類了,User是我們要創建的類的名稱。通過“:”和縮進來表明所有縮進的代碼將會是這個類里的內容。從User類中創建一個該類的實例通過下面的寫法:
""" 創建一個實例,通過類名加括號的形式,類似調用函數 """ u=User()
對象(客體)有自己的特征和自己可以做到的事,對應到程序里就是字段(field) 和方法(method) ,這兩個都是對象的屬性(attribute) 。對象的字段類似于普通變量,所不同的是對象的字段是對象獨有的。對象的方法類似于普通函數,所不同的是對象的方法是對象獨有的。上篇文章中我們已經見到過如何使用字段跟方法,那就是通過.操作符。
1.0.定義方法(method)在類中定義對象的方法(method)比較簡單,跟實現普通函數類似,只有一點不同,那就是不管方法需不需要參數,你都需要把self作為一個參數名傳進去,self這個參數在我們調用方法時我們可以直接忽略,不賦值給它。舉個例子:
class User: def hi(self): print("hi!") u=User() u.hi() """ 程序輸出: hi! """
self這個參數名是約定俗成的。在User類的代碼塊里定義hi方法時,傳入的參數self將會是某個實例(對象)本身。當u作為User類的實例被創建,并且通過u.hi()調用hi方法時,python解釋器會自動將其轉換成User.hi(u)。通過傳入實例(對象)本身,也即self,方法(method)就能夠訪問實例的字段(filed),并對其進行操作,我們之后可以從新的例子中看到。
1.1.聲明字段(field)要在類中聲明對象的字段,有一個特殊的方法(method)可以做到,那就是__init__方法,這個方法在init前后都要寫上兩個下劃線__。__init__方法會在實例一開始創建的時候就被調用,init是initialization的縮寫,顧名思義,就是初始化的意思。__init__方法在創建對象的時候由python解釋器自動調用,不需要我們手動來調用。看個例子:
class User: """ 注意self總是在括號里的最左邊 """ def __init__(self,name,age): self.name=name self.age=age def hi(self): print("hi!I"m {}".format(self.name)) u=User("li",32) u.hi() print(u.name+","+str(u.age)) """ 程序輸出: hi!I"m li li,32 """
上面的代碼里,在User類的__init__方法中self被傳入,那么就可以通過self.name跟self.age來聲明對象的兩個字段,并將傳入該方法的參數name跟age賦值給它們。當我們創建類型為User的對象的時候,傳入實際的參數"li"跟32,這兩個參數被python解釋器傳入__init__方法中,"li"對應name,32對應age,__init__方法立即被調用,將實例u的字段一一建立。
self.name=name粗看貌似都是name變量,但self.name是實例的字段,專屬于實例,name是創建對象時將要傳入的一個參數,將它賦值給self.name是沒有歧義的。
1.2.實例變量與類變量事實上,字段除了獨屬于實例之外,跟普通變量沒有什么差別,所以實例的字段也被稱為實例變量。在類的定義中,與實例變量對應的還有類變量,類變量與實例變量類似,通過.操作符來訪問。類變量是任何實例共享的,可以理解為是該類型所共有的特征,比如,在User類中,我們可以計算一共有多少被實例化了的用戶:
class User: """ 計算被實例化的用戶數量 """ count=0 def __init__(self,name,age): """每次創建一個對象,用戶數量在原有基礎上加1""" User.count=User.count+1 self.name=name self.age=age def hi(self): print("hi!I"m {}".format(self.name)) def die(self): print("I"m {}, dying...".format(self.name)) User.count=User.count-1 del self @classmethod def print_count(cls): print("共有{}名用戶".format(cls.count)) u=User("li",32) User.print_count() u.hi() u1=User("ma",30) u1.__class__.print_count() u1.hi() u.die() User.print_count() u1.die() User.print_count() """ 程序輸出: 共有1名用戶 hi!I"m li 共有2名用戶 hi!I"m ma I"m li, dying... 共有1名用戶 I"m ma, dying... 共有0名用戶 """
上面代碼中的count就是類變量,可以通過User.count來訪問。python通過@classmethod來表明它下面定義的方法是類的方法(method),類的方法中的cls是類本身,跟self使用方法類似,調用類方法時可以直接忽略。類變量跟類的方法(method)都可以被稱為類的成員。除了使用類似User.count這樣的方式來訪問和使用之外,該類的實例還可以通過__class__屬性來訪問和使用類成員,比如上面代碼中的u1.__class__.print_count()。
上面代碼中定義的字段跟方法都是公開的,可以通過.操作符訪問。但如果屬性是以形如__name這樣以雙下劃線為開頭的名稱,則python會自動將名稱換成_classname__name,其中classname就是類的名稱,這樣,通過an_object.__name是訪問不到的。
1.3.繼承(inherit)現實世界里,某一類相似客體的類型被抽象為一個概念(名稱),同時又有另一類相似客體的類型被抽象為一個概念(名稱),這時候我們可能會發現,這兩個概念(名稱)之間很相似,于是我們把這兩個相似概念再抽象成一個概念(名稱),這樣的過程可以重復多次。舉個例子,我們有幼兒園,同時有小學,這時候我們可以把幼兒園跟小學抽象成學校。那跟現實類似,對象的類型跟類型之間,可以抽象成另一個類型。在文章最開頭我們說面向對象編程是自頂而下,這跟一層層向上抽象的過程正好相反,我們會在一開始思考如何創建某個類,然后把這個類作為基類(父類) ,更具體的創建一(幾)個新類,這一(幾)個新類不僅擁有基類的屬性,還會增加自己獨有的屬性。我們把這樣的新類(class)叫做子類,是從基類繼承而來的。
從1.2小節的代碼例子中,我們創建了一個User(用戶)類,假如我們現在需要區分免費用戶和付費用戶,免費用戶跟付費用戶都具有name、age、hi屬性,同時免費用戶跟付費用戶還具有自己獨有的一些屬性。如果我們直接分別創建免費用戶類跟付費用戶類,那么這兩個類之間共同的屬性就會被定義兩次,需要復制粘貼同樣的代碼,這樣的代碼質量不高并且不夠簡潔。相反,我們可以讓免費用戶跟付費用戶都從User類繼承下來,這樣就可以重用(reuse) User類的代碼,并且讓代碼相對簡潔高效一點。還有一個好處就是,當免費用戶跟付費用戶之間共同的屬性有了變化(增減),我們可以直接修改父類User,而不用分別修改,當子類的數量很多時,這種好處會顯得非常明顯。修改父類之后,各子類的獨有屬性不會受到影響。
1.2小節的代碼例子中,我們對實例的數量進行統計,當子類從User類繼承下來后,在子類實例化對象的時候,父類的實例數量在python中默認也會增多,這說明我們可以把子類的實例看做是父類的實例,這被稱為多態性(polymorphism)。免費用戶類跟付費用戶類如何從父類繼承,如下:
class User: """ 計算被實例化的用戶數量 """ count=0 def __init__(self,name,age): """每次創建一個對象,用戶數量在原有基礎上加1""" User.count=User.count+1 self.name=name self.age=age def hi(self): print("hi!I"m {}".format(self.name)) def die(self): print("I"m {}, dying...".format(self.name)) User.count=User.count-1 del self @classmethod def print_count(cls): print("共有{}名用戶".format(cls.count)) class Free_user(User): def __init__(self,name,age,number_of_ads): User.__init__(self,name,age) self.number_of_ads=number_of_ads def hi(self): User.hi(self) print("I"m free_user") class Paying_user(User): def __init__(self,name,age,plan): User.__init__(self,name,age) self.plan=plan def hi(self): print("hi!I"m {},paying_user".format(self.name,)) u=Free_user("li",32,5) User.print_count() u.hi() u1=Paying_user("ma",30,"5$") User.print_count() u1.hi() u.die() User.print_count() u1.die() User.print_count() """ 程序輸出: 共有1名用戶 hi!I"m li I"m free_user 共有2名用戶 hi!I"m ma,paying_user I"m li, dying... 共有1名用戶 I"m ma, dying... 共有0名用戶 """
上面代碼中首先創建了User基類,然后從User類繼承下來兩個子類:Free_user跟Paying_user。子類要從某個類繼承而來,需要在類名后面跟上括號,在括號中填入基類的名稱,形如這樣:Free_user(User)。
在子類的__init__方法中,通過調用基類的__init__方法把繼承自基類的共有字段創建出來,調用的時候將self跟傳入的屬于基類部分的參數原樣傳入,上面代碼中將傳入的name跟age原樣傳入了基類的__init__方法中了。因為我們在子類中定義了__init__方法,所以python不會自動調用基類的__init__方法,而需要我們顯式地調用它。如果子類中沒有定義__init__方法,則python會在創建子類的實例時自動調用基類的__init__方法。這是為什么呢?我們在子類中對基類的hi方法進行了重寫,但對die方法則沒有,但是我們通過子類創建的實例能夠調用die方法,這說明在調用die方法時子類的實例被看做了父類的實例了,同時在調用hi方法時卻都調用了重寫的子類中的方法了,這說明,當實例調用一個方法時,會首先在實例化自己的類中尋找對該方法的定義,如果沒有,則在該類的父類中尋找,如果父類中還是沒有,則會在父類的父類中尋找,這樣的過程一直重復,直到再也沒有父類了,如果還是沒有該方法,那程序就會報錯。
最后,從前一小節我們知道,以雙下劃線為前綴的屬性名會被python自動替換成另一個名稱,這時候當父類中有個以雙下劃線為前綴的屬性,我們在子類中也有一個相同的屬性的時候,由于python自動替換了這兩個屬性名稱,子類中的方法并沒有覆蓋掉父類中的屬性,而只是,子類中的該屬性跟父類中的該屬性是兩個不同的屬性。看例子:
#其他一些屬性用“,”省略了 >>> class a: ... def __a_method(self): ... pass ... >>> dir(a()) ["__class__", ,,, "_a__a_method"] >>> class b(a): ... def __a_method(self): ... pass ... >>> dir(b()) ["__class__", ,,, "_a__a_method", "_b__a_method"] >>>
可以看到,__a_method在a類的實例中被替換成了_a__a_method,在b類的實例中被替換成了_b__a_method,這跟_a_a_method不同,b類的實例中繼承了_a_a_method,同時,還有自己類型的_b_a_method。
1.4.類(class),對象(object)與類型(type)之間的關系看個例子:
>>> list.__base__>>> type(list) >>> class User: ... pass ... >>> User.__base__ >>> type(list)
類的屬性__base__可以指明該類是繼承自哪個類。從上面的例子可以看到,python中定義內置數據類型的類(class)(以list為例)從object類繼承而來,但是其類型(type)是type類,我們自己創建并且沒有明確指定從是哪個類繼承而來的類(以User為例),跟內置類型一樣,從object類繼承而來,其類型(type)是type類。如果我們自己創建的,并且從層次更低的類(比如User類)中繼承而來,那么該類的__base__(基類)跟type函數所顯示的類型是怎么樣的呢,接上面的例子:
>>> Free_user.__base__>>> type(Free_user) >>>
可以看到,Free_user從User繼承而來,但其類型依然是type,所以所有的類(class)的類型應當都是type,是不是這樣呢,object會不會例外呢?type自己呢?它的類型呢?下面的例子驗證了它們的類型也是type:
>>> type(object)>>> type(type)
從例子中我們知道了,所有的類(class)的類型都是type類,所以,所有的類(class)都是從type類中實例化出來的實例,甚至type類實例化了自己,所以所有的類包括object和type都是實例,這說明所有的類都是對象 。為了清晰的指代不同種類的對象,我們把本身是類的對象稱為類對象,把從類中實例化而來并且本身不是類的對象成為實例對象,實例對象是層次最低的對象。
我們可以看到object類跟type類是python中抽象層次最高的對象了。那么它們兩個的關系如何呢?看例子:
>>> type.__base__>>> print(object.__base__) None >>>
可以看到,type從object繼承而來,并且我們已經知道object的類型是type。從例子中可以看到object沒有基類,所以如果把類的層層繼承想象成一條鎖鏈,那么object將是繼承鏈上的頂點。前面我們提到的對象的一些特殊屬性如__init__、__class__繼承自object,而__base__這個特殊屬性則只有類對象才有,是在type中定義的。如下:
#其他一些屬性用“,”省略了 >>> dir(object) ["__class__", ,,, "__init__", ,,, "__subclasshook__"] >>> dir(type) ["__abstractmethods__", "__base__", "__bases__", ,,, "mro"] >>>
[歡迎瀏覽我的個人博客,https://diwugebingren.github.io
](https://diwugebingren.github....
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44058.html
摘要:如果還是沒有找到,就會使用父類中的元類來創建類。元類通常用于處理比較復雜的情況。這是因為使用了元類,它會將中定義的字段轉換成數據庫中的字段。中所有數據類型都是對象,它們要么是類的實例要么是元類的實例。 原文地址:what is metaclass in Python?我的簡書地址::nummy 類即對象 在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallT...
摘要:可以使用標準的索引切片迭代操作訪問它,其中每項操作均鎖進程同步,對于字節字符串,還具有屬性,可以把整個數組當做一個字符串進行訪問。當所編寫的程序必須一次性操作大量的數組項時,如果同時使用這種數據類型和用于同步的單獨大的鎖,性能將極大提升。 上一篇文章:Python進程專題5:進程間通信下一篇文章:Python進程專題7:托管對象 我們現在知道,進程之間彼此是孤立的,唯一通信的方式是隊...
摘要:原鏈接中的元類是什么類也是對象在理解元類之前,需要掌握中類概念。事實上,是中用于創建所有類的元類。類本身是元類的對象在中,除了,一切皆對象,一切都是類或者元類的對象。事實上是自己的元類, 學習契機 項目中使用Elasticsearch(ES)存儲海量業務數據,基于ES向外提供的API進一層封裝,按需處理原始數據提供更精確、更多樣化的結果。在研究這一層的代碼時接觸到@six.add_me...
摘要:對于中的列表,情況并非如此。現在我們將繼續討論如何在列表中添加新元素以及更多內容。可變性意味著改變其行為的能力。該位置是元素需要保留在列表中的位置。默認值是列表的最大允許索引,即列表的長度。用于給出列表的長度,即列表中存在的元素的數量。 showImg(https://segmentfault.com/img/remote/1460000019111677); 來源 | 愿碼(Cha...
摘要:說起這個函數就需要先了解的變量存儲機制了變量是動態變量,不用提前聲明類型。當我們寫時,解釋器干了兩件事情在內存中創建了一個的字符串在內存中創建了一個名為的變量,并把它指向。 id(object) Return the identity of an object. This is an integer (or long integer) which is guaranteed to be...
閱讀 2371·2023-04-25 20:07
閱讀 3307·2021-11-25 09:43
閱讀 3666·2021-11-16 11:44
閱讀 2532·2021-11-08 13:14
閱讀 3182·2021-10-19 11:46
閱讀 898·2021-09-28 09:36
閱讀 2984·2021-09-22 10:56
閱讀 2377·2021-09-10 10:51