標籤:規範 面試 python類 圓點 先後 命名方式 lan method oom
0x00 前言
類,在學習物件導向我們可以把類當成一種規範,這個思想就我個人的體會,感覺很重要,除了封裝的功能外,類作為一種規範,我們自己可以定製的規範,從這個角度來看,在以後我們學習設計模式的時候,對設計模式的理解會很有協助。其次,語言中類是抽象的模板,用來描述具有相同屬性和方法的對象的集合,比如Animal類。而執行個體是根據類建立出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的資料可能不同。
Python使用class關鍵字來定義類,其基本結構如下:
class 類名(父類列表): pass
類名通常採用駝峰式命名方式,盡量讓字面意思體現出類的作用。Python採用多繼承機制,一個類可以同時繼承多個父類(也叫基類、超類),繼承的基類有先後順序,寫在類名後的圓括弧裡。繼承的父類列表可以為空白,此時的圓括弧可以省略。但在Python3中,即使你採用類似classStudent:pass的方法沒有顯式繼承任何父類的定義了一個類,它也預設繼承object類。因為,object是Python3中所有類的基類。
下面是一個學生類:
class Student: classroom = ‘101‘ address = ‘beijing‘ def __init__(self, name, age): self.name = name self.age = age def print_age(self): print(‘%s: %s‘ % (self.name, self.age))
可以通過調用類的執行個體化方法(有的語言中也叫初始化方法或建構函式)來建立一個類的執行個體。預設情況下,使用類似obj=Student()的方式就可以產生一個類的執行個體。但是,通常每個類的執行個體都會有自己的執行個體變數,例如這裡的name和age,為了在執行個體化的時候體現執行個體的不同,Python提供了一個def__init__(self):的執行個體化機制。任何一個類中,名字為__init__的方法就是類的執行個體化方法,具有__init__方法的類在執行個體化的時候,會自動調用該方法,並傳遞對應的參數。比如:
class Student:li = Student("李四", 24)zhang = Student("張三", 23)
0x01 執行個體變數和類變數
執行個體變數:
執行個體變數指的是執行個體本身擁有的變數。每個執行個體的變數在記憶體中都不一樣。Student類中__init__方法裡的name和age就是兩個執行個體變數。通過執行個體名加圓點的方式調用執行個體變數。
我們列印下面四個變數,可以看到每個執行個體的變數名雖然一樣,但他們儲存的值卻是各自獨立的:
print(li.name)print(li.age)print(zhang.name)print(zhang.age)------------------------李四24張三23
類變數:
定義在類中,方法之外的變數,稱作類變數。類變數是所有執行個體公有的變數,每一個執行個體都可以訪問、修改類變數。在Student類中,classroom和address兩個變數就是類變數。可以通過類名或者執行個體名加圓點的方式訪問類變數,比如:
Student.classroomStudent.addressli.classroomzhang.address
在使用執行個體變數和類變數的時候一定要注意,使用類似zhang.name訪問變數的時候,執行個體會先在自己的執行個體變數列表裡尋找是否有這個執行個體變數,如果沒有,那麼它就會去類變數列表裡找,如果還沒有,彈出異常。
Python動態語言的特點,讓我們可以隨時給執行個體添加新的執行個體變數,給類添加新的類變數和方法。因此,在使用li.classroom = ‘102‘的時候,要麼是給已有的執行個體變數classroom重新賦值,要麼就是建立一個li專屬的執行個體變數classroom並賦值為‘102’。看下面的例子
>>> class Student: # 類的定義體 classroom = ‘101‘ # 類變數 address = ‘beijing‘ def __init__(self, name, age): self.name = name self.age = age def print_age(self): print(‘%s: %s‘ % (self.name, self.age))>>> li = Student("李四", 24) # 建立一個執行個體>>> zhang = Student("張三", 23) # 建立第二個執行個體>>> li.classroom # li本身沒有classroom執行個體變數,所以去尋找類變數,它找到了!‘101‘>>> zhang.classroom # 與li同理‘101‘>>> Student.classroom # 通過類名訪問類變數‘101‘>>> li.classroom = ‘102‘ # 關鍵的一步!實際是為li建立了專屬的執行個體變數,只不過名字和類變數一樣,都叫做classroom。>>> li.classroom # 再次訪問的時候,訪問到的是li自己的執行個體變數classroom‘102‘>>> zhang.classroom # zhang沒有執行個體變數classroom,依然訪問類變數classroom‘101‘>>> Student.classroom # 保持不變‘101‘>>> del li.classroom # 刪除了li的執行個體變數classroom>>> li.classroom # 一切恢複了原樣‘101‘>>> zhang.classroom‘101‘>>> Student.classroom‘101‘
0x03 類的方法
Python的類中包含執行個體方法、靜態方法和類方法三種方法。這些方法無論是在代碼編排中還是記憶體中都歸屬於類,區別在於傳入的參數和調用方式不同。在類的內部,使用def關鍵字來定義一個方法。
執行個體方法
類的執行個體方法由執行個體調用,至少包含一個self參數,且為第一個參數。執行執行個體方法時,會自動將調用該方法的執行個體賦值給self。self代表的是類的執行個體,而非類本身。self不是關鍵字,而是Python約定成俗的命名,你完全可以取別的名字,但不建議這麼做。
例如,我們前面Student類中的print_age()就是執行個體方法:
def print_age(self): print(‘%s: %s‘ % (self.name, self.age))# --------------------------# 調用方法li.print_age()zhang.print_age()
靜態方法
靜態方法由類調用,無預設參數。將執行個體方法參數中的self去掉,然後在方法定義上方加上@staticmethod,就成為靜態方法。它屬於類,和執行個體無關。建議只使用類名.靜態方法的調用方式。(雖然也可以使用執行個體名.靜態方法的方式調用)
class Foo: @staticmethod def static_method(): pass#調用方法Foo.static_method()
類方法
類方法由類調用,採用@classmethod裝飾,至少傳入一個cls(代指類本身,類似self)參數。執行類方法時,自動將調用該方法的類賦值給cls。建議只使用類名.類方法的調用方式。(雖然也可以使用執行個體名.類方法的方式調用)
class Foo: @classmethod def class_method(cls): passFoo.class_method()
看一個綜合例子:
class Foo: def __init__(self, name): self.name = name def ord_func(self): """定義執行個體方法,至少有一個self參數 """ print(‘執行個體方法‘) @classmethod def class_func(cls): """ 定義類方法,至少有一個cls參數 """ print(‘類方法‘) @staticmethod def static_func(): """ 定義靜態方法 ,無預設參數""" print(‘靜態方法‘) # 調用執行個體方法f = Foo("Jack")f.ord_func()Foo.ord_func(f) # 請注意這種調用方式,雖然可行,但建議不要這麼做!# 調用類方法Foo.class_func()f.class_func() # 請注意這種調用方式,雖然可行,但建議不要這麼做!# 調用靜態方法Foo.static_func()f.static_func() # 請注意這種調用方式,雖然可行,但建議不要這麼做!
0x04 類、類的方法、類變數、類的執行個體和執行個體變數在記憶體中是如何儲存的
類、類的所有方法以及類變數在記憶體中只有一份,所有的執行個體共用它們。而每一個執行個體都在記憶體中獨立的儲存自己和自己的執行個體變數。
建立執行個體時,執行個體中除了封裝諸如name和age的執行個體變數之外,還會儲存一個類對象指標,該值指向執行個體所屬的類的地址。因此,執行個體可以尋找到自己的類,並進行相關調用,而類無法尋找到自己的某個執行個體。
0x05 Python 類的繼承
在ptyhon中類一個類是可以同時繼承多個類,文法:
class 類名(父類1,父類2,...)類體
Python類繼承之深度優先
python 支援多繼承,但對與經典類和新式類來說,多繼承尋找的順序是不一樣的。
經典類:
class P1: def foo(self): print ‘p1-foo‘ class P2 : def foo(self): print ‘p2-foo‘ def bar(self): print ‘p2-bar‘ class C1 (P1,P2): pass class C2 (P1,P2): def bar(self): print ‘C2-bar‘ class D(C1,C2): pass
執行個體d調用foo()時,搜尋順序是 D => C1 => P1
執行個體d調用bar()時,搜尋順序是 D => C1 => P1 => P2
換句話說,經典類的搜尋方式是按照“從左至右,深度優先”的方式去尋找屬性。d先尋找自身是否有foo方法,沒有則尋找最近的父類C1裡是否有該方法,如果沒有則繼續向上尋找,直到在P1中找到該方法,尋找結束。
Python類繼承之廣度優先
新式類:
class P1(object): def foo(self): print ‘p1-foo‘ class P2(object): def foo(self): print ‘p2-foo‘ def bar(self): print ‘p2-bar‘ class C1 (P1,P2): pass class C2 (P1,P2): def bar(self): print ‘C2-bar‘ class D(C1,C2): pass
執行個體d調用foo()時,搜尋順序是 D => C1 => C2 => P1
執行個體d調用bar()時,搜尋順序是 D => C1 => C2
可以看出,新式類的搜尋方式是採用“廣度優先”的方式去尋找屬性。
參考
Python面試題之Python中的類和執行個體