Python學習:類和例項

玄魂發表於2018-08-08

Python學習:類和例項

本文作者: 玄魂工作室--熱熱的螞蟻


,在學習物件導向我們可以把類當成一種規範,這個思想就我個人的體會,感覺很重要,除了封裝的功能外,類作為一種規範,我們自己可以定製的規範,從這個角度來看,在以後我們學習設計模式的時候,對設計模式的理解會很有幫助。其次,語言中類是抽象的模板,用來描述具有相同屬性和方法的物件的集合,比如Animal類。而例項是根據類建立出來的一個個具體的“物件”,每個物件都擁有相同的方法,但各自的資料可能不同。

Python使用class關鍵字來定義類,其基本結構如下:

class 類名(父類列表):
    pass
複製程式碼

image

類名通常採用駝峰式命名方式,儘量讓字面意思體現出類的作用。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)
複製程式碼

例項變數和類變數

例項變數

例項變數指的是例項本身擁有的變數。每個例項的變數在記憶體中都不一樣。Student類中__init__方法裡的name和age就是兩個例項變數。通過例項名加圓點的方式呼叫例項變數。

我們列印下面四個變數,可以看到每個例項的變數名雖然一樣,但他們儲存的值卻是各自獨立的:

print(li.name)
print(li.age)
print(zhang.name)
print(zhang.age)
------------------------
李四
24
張三
23
複製程式碼

類變數

定義在類中,方法之外的變數,稱作類變數。類變數是所有例項公有的變數,每一個例項都可以訪問、修改類變數。在Student類中,classroom和address兩個變數就是類變數。可以通過類名或者例項名加圓點的方式訪問類變數,比如:

Student.classroom
Student.address
li.classroom
zhang.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'
複製程式碼

類的方法:

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):
        pass

Foo.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() # 請注意這種呼叫方式,雖然可行,但建議不要這麼做!
複製程式碼

類、類的方法、類變數、類的例項和例項變數在記憶體中是如何儲存的?

類、類的所有方法以及類變數在記憶體中只有一份,所有的例項共享它們。而每一個例項都在記憶體中獨立的儲存自己和自己的例項變數。

建立例項時,例項中除了封裝諸如name和age的例項變數之外,還會儲存一個類物件指標,該值指向例項所屬的類的地址。因此,例項可以尋找到自己的類,並進行相關呼叫,而類無法尋找到自己的某個例項。

image.gif

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 可以看出,新式類的搜尋方式是採用“廣度優先”的方式去查詢屬性。

本文首發於玄魂工作室微信訂閱號

image

更多內容,訂閱號回覆“python”。

Python原始碼學習筆記(一)編譯與安裝

Python 黑客相關電子資源和書籍推薦

Python黑帽程式設計 4.1 Sniffer(嗅探器)之資料捕獲(上)

相關文章