Python 和 JavaScript一樣即是面向過程語言,也是面向對象語言,動態語言。大多數面向對象語言里,Class是必不可少的。面向對象有三大特性:封裝, 繼承,多態。在Python中Class到底是怎樣的呢? 1、Class組成 2、Class getter, setter 3、Class繼 ...
Python 和 JavaScript一樣即是面向過程語言,也是面向對象語言,動態語言。大多數面向對象語言里,Class是必不可少的。面向對象有三大特性:封裝, 繼承,多態。在Python中Class到底是怎樣的呢?
1、Class組成
先來看一個示例:
class Person(object): id='' name = '' age = 3 # 等同於Java中的<init>,即構造器 def __init__(self, id, name, age): print("init a Person instance") self.id = id self.name = name self.age = age def show(this): print(this) # print(this.toString()) #def toString(self): # return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同於Java中的toString def __str__(self): # return self.toString() return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同於Java中的finalize方法,del 實例時調用 def __del__(self): print("finally a Person instance") self.id = None self.name = None self.age = None self = None
下麵是以對比Java的方式,來說明Python中的類:
1)Class中,包括屬性、方法,它們都是public的。在Python的Class中,是不存在private,protected等修飾符的。
2)__init__是構造函數,調用構造器時,會自動調用__init__。它相當於Java中的<init>。在創建一個Python對象時,不需要像Java那樣使用new。
3)__del__是析構函數,當del instance時,會自動調用__del__。它相當於Java中的finalize
4)需要獲取對象的字元串表示時,會調用__str__。它就相當於Java中的toString。
5)類的方法的第一個參數,都是self,相當於Java中的this。其實Java中的實例方法的第一個參數也是this,只是被隱藏了而已,如果你瞭解JVM 運行時的話,或者使用過 javassist或者asm等位元組碼工具的話,會知道的。上面的示例中的p1.show()運行時,可以理解為執行的是Person.show(p1)。 並不是說必須得寫成self,你也可以寫完this或者其他任何的滿足變數命名的形式。但通常大家都約定俗稱的寫為self,保持編碼風格的統一,有利於方便他人理解代碼。
6)__init__,__del__,__str__不是必須的。
7)所有屬性都要有初始值
2、Class getter,setter
Java,JavaScript ES6都支持setter,getter。Python中也是支持的。
class Person(object): id='' name = '' age = 3 # 等同於Java中的<init>,即構造器 def __init__(self, id, name, age): print("init a Person instance") self.id = id self.name = name self.age = age def show(this): print(this) # print(this.toString()) #def toString(self): # return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同於Java中的toString def __str__(self): # return self.toString() return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同於Java中的finalize方法,del 實例時調用 def __del__(self): print("finally a Person instance") self.id = None self.name = None self.age = None self = None def __get__(self, name): print("invoke in __get__") print(self.__dict__) return 1111 def __getattr__(self, name): print("invoke in __getattr__") return 1111 def __getattribute__(self, name): print("invoke in __getattribute__") print(object.__getattribute__(self, name)) print("after invoke in __getattribute__") return object.__getattribute__(self, name)
對於舊式類,訪問屬性的順序是:
1)直接訪問屬性
2)訪問__getattr__
對於新式類,訪問屬性的順序是:
1)__getattribute
2)直接訪問屬性
3)__getattr__
切記,如果寫了__getattribute__,最後一句話必須是object.__getattribute(self,name) 否則就會出現:’XxxType’ object is not callable 。它的存在,更像是作為攔截器使用。
3、Class繼承
C++是多繼承的,Java是單繼承的,Python借鑒了C++的多繼承方式。
在繼承結構下,訪問屬性,方法時,與Java中一樣的,先自己的,自己沒有時,才去從父類找。
由於支持多繼承,所以當一個屬性、方法,在多個父類中都存在時,會訪問到哪個呢?
要解答這個問題,就得先知道搜索屬性、方法的順序。採用的搜索演算法是深度優先搜索演算法。
也就是一個class A(S1,S2,S3):pass; 要調用一個屬性時,會先從A里找,找不到再從S1,如果還找不到再從S2,依次類推。
那麼在多繼承情況下,如果A,S1,S2,S3都重寫了__getattr__方法,那會有什麼區別呢?查找順序大體不變的:
1) A的直接屬性,找不到然後是A.__getattr__
2) S1的直接屬性,找不到然後是S1.__getattr__
3) S2的直接屬性,找不到然後是S2.__getattr__
4) S3的直接屬性,找不到然後是S3.__getattr__
對於構造函數__init__,在構造實例時,不會像Java那樣,先去調用父類的__init__。
class Student(Person): def __init__(self, id, name, age,email): print("invoke in Student __init__") super(Student, self).__init__(id, name, age) self.email = email
Java中有super(),Python中是否有呢?能否利用super呢?如果不能自動調用父類的構造器,那麼在重寫__init__又得給所有的屬性都分配一下,這個好麻煩的,該怎麼辦呢?只能以編程的方式自己調用了。
super(類,instance),這個函數的作用是找到指定的類的下一個父類的指定方法,將該方法在指定的實例上調用。
例如上面的例子中,繼承關係是這樣的:Student > Person > object。self是Student對象,super(Student, self).__init__(id, name, age)該語句的意思就是:找到Student的下一個父類(即Person)的__init__方法,然後在self上調用該__init__方法。
需要註意的是:
1)super函數的兩個參數,不能有誤。需要滿足兩個條件,第二個instance應該是第一個參數所代表的類的實例(或者是其子類的實例)。
2)Super只能在新式類中使用。
4、運算符重寫
在.Net和Scala編程語言中,都是支持運算符重寫的。如果沒有接觸過這些東西,可能會費解的。其實只要把運算符看著是方法就可以了。從這個角度來理解的話,Java也是支持運算符重寫的,只不過呢,該特性並沒有暴露給用戶罷了。例如字元串拼接 + ,它其實是調用的StringBuilder.append方法,遍歷集合的foreach,它其實調用的是iterator。既然提到了運算符重寫,那麼Python中也必然是支持的,並且它把這個特性暴露出來了。
Python String 就重寫了幾個運算符:
+ 字元串拼接 * copy多份 == 比較字元串內容 > 字元串比較 < 字元串比較
那麼怎樣實現運算符重寫呢?
上面已經說明,將運算符看作是一個方法。那麼重寫運算符,就是就是重寫方法了。
下麵列出了常見的運算符重載:
重載方法 |
說明 |
調用 |
__init__ |
構造器 |
對象建立,X=Class() |
__del__ |
析構方法 |
對象回收,或者del X |
__add__ |
運算符 + |
X+Y, X+=Y |
__iadd__ |
增強的 + |
X+Y, X+=Y |
__radd__ |
左側+ |
Noninstance + Y |
__or__ |
運算符 | (位 OR) |
X|Y, X|=Y |
__repr__,__str__ |
列印,轉換 |
print(X), repr(X),str(X) |
__getattr__ |
點號運算符 |
獲取屬性 |
__setattr__ |
賦值運算符 |
設置屬性 |
__call__ |
函數調用 |
Y() |
__len__ |
長度 |
len(X) |
__cmp__, __lt__, __eq__ |
比較 |
X < Y X==Y |
__getitem__ |
索引運算符 |
X[name] |
__setitem__ |
索引賦值 |
X[Y]=Z |
__iter__ |
迭代 |
迴圈,迭代等 |
使用__getitem__可以使得對象具備索引的方式來訪問?
測試如下:對上面的Student如下:
class Student(Person): def __init__(self, id, name, age,email): print("invoke in Student __init__") super(Student, self).__init__(id, name, age) self.email = email def __getitem__(self, name): return self.__dict__[name] + "_suffix"
執行測試:
import model Student = model.Student s = Student('0001', 'fjn', 20, '[email protected]') s.show() print(s["email"])
發現結果是:[email protected]_suffix
5、模擬私有屬性
通過上面的學習,瞭解到Python的屬性、方法都是public, 這點和JavaScript太 一樣了。寫Java時間久了,私有屬性的好處,怎麼可以浪費了呢。那麼如何模擬出私有的?
有兩種方式可以模擬的,都與JavaScript里的理念是類似的。
方式一:使用對象時,直接加屬性。
方式二:利用對象的__dict__
6、Static Method
在java中,會將實例方法與類方法區分,具體做法是在 class 方法上加上static修飾符。在python中是沒有static的,那麼如何實現static方法。
前面也講了,實例方法聲明時,第一個參數是self。在python中,實例方法本質就是調用類方法的。
p1=Person(‘a’,’b’,23)
Person.show(p1)