Python學習:類和實例
本文作者: 玄魂工作室--熱熱的螞蟻
類,在學習面向對象我們可以把類當成一種規范,這個思想就我個人的體會,感覺很重要,除了封裝的功能外,類作為一種規范,我們自己可以定制的規范,從這個角度來看,在以后我們學習設計模式的時候,對設計模式的理解會很有幫助。其次,語言中類是抽象的模板,用來描述具有相同屬性和方法的對象的集合,比如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)
實例變量和類變量
實例變量:
實例變量指的是實例本身擁有的變量。每個實例的變量在內存中都不一樣。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的實例變量之外,還會保存一個類對象指針,該值指向實例所屬的類的地址。因此,實例可以尋找到自己的類,並進行相關調用,而類無法尋找到自己的某個實例。
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”。