1. 創建自己的類
學習面向對象的第一步,就是創建一個類。因為類是面向對象的基石。Python類和其他編程語言(Java、C#等)的類差不多,也需要使用class關鍵字。下面通過一個實際的例子來看一下Python類是如何創建的。
本例會創建一個類,以及利用這個類創建兩個對象,並調用其中的方法。
1 # 創建一個Person類 2 class Person: 3 # 定義setName方法 4 def setName(self, name): 5 self.name = name 6 # 定義getName方法 7 def getName(self): 8 return self.name 9 # 定義greet方法 10 def greet(self): 11 print("Hello, I'm {name}.".format(name = self.name)) 12 13 # 創建Person對象 14 person1 = Person() 15 # 創建Person對象 16 person2 = Person() 17 # 調用person1對象的setName方法 18 person1.setName("Bill Gates") 19 # 調用person2對象的name屬性 20 person2.name = "Bill Clinton" 21 # 調用person1對象的getName方法 22 print(person1.getName()) 23 # 調用person1對象的greet方法 24 person1.greet() 25 # 調用person2對象的屬性 26 print(person2.name) 27 # 調用person2對象的greet方法,另外一種調用方法的方式 28 Person.greet(person2)
程序運行結果如下圖所示。
從上面的代碼我們可以了解到Python類的如下知識點。
- Python類使用class關鍵字定義,類名直接跟在class關鍵字的后面。
- 類也是一個代碼塊,所以類名后面要跟着一個冒號(:)。
- 類中的方法其實就是函數,定義的方法也完全一樣,只是由於函數定義在類的內部,所以為了區分,將定義在類內部的函數稱為方法。
- 我們可以看到,每一個方法的第1個參數都是self,其實這是必須的。這個參數名不一定叫self(可以叫abc或任何其他名字),但任意一個方法必須至少指定一個self參數,如果方法中包含多個參數,第1個參數將作為self參數使用。在調用方法時,這個參數的值不需要自己傳遞,系統會將方法所屬的對象傳入這個參數。在方法內部可以利用這個參數調用對象本身的資源,如屬性、方法等。
- 通過self參數添加的name變量是Person類的屬性,可以在外部訪問。本例設置了person2對象的name屬性的值,與調用person2.setName方法的效果完全相同。
- 使用類創建對象的方式與調用函數的方式相同。在Python語言中,不需要像Java一樣使用new關鍵字創建對象,只需要用類名加上構造方法(在后面的章節會詳細介紹)參數值即可。
- 調用對象的方法有兩種方式,一種是直接通過對象變量調用方法,另一種是通過類調用方法,並且將相應的對象傳入方法的第1個參數。在本例中使用了Person.greet(person2)的方式調用了person2對象中的greet方法。
如果使用集成開發環境,如PyDev、PyCharm,那么代碼編輯器也會對面向對象有很好的支持,例如,當在對象變量后輸入一個點(.)后,IDE會為我們列出該對象中所有可以調用的資源,包括方法和屬性,如下圖所示。
2.方法和私有化
Python類默認情況下,所有的方法都可以被外部訪問。不過像很多其他編程語言,如Java、C#等,都提供了private關鍵字將方法私有化,也就是說只有類的內部方法才能訪問私有化的方法,通過正常的方式是無法訪問對象的私有化方法的(除非使用反射技術,這就另當別論了)。不過在Python類中並沒有提供private或類似的關鍵字將方法私有化,但可以曲線救國。
在Python類的方法名前面加雙下划線(__)可以讓該方法在外部不可訪問。
1 class Person: 2 # method1方法在類的外部可以訪問 3 def method1(self): 4 print("method1") 5 # __method2方法在類的外部不可訪問 6 def __method2(self): 7 print("method2") 8 9 p = Person() 10 p.method1() 11 p.__method2() # 拋出異常
如果執行上面的代碼,會拋出如下圖所示的異常信息,原因是調用了私有化方法method2。
其實“method2”方法也不是絕對不可訪問。Python編譯器在編譯Python源代碼時並沒有將“method2”方法真正私有化,而是一旦遇到方法名以雙下划線(__)開頭的方法,就會將方法名改成“ClassNamemethodName”的形式。其中ClassName表示該方法所在的類名,“methodName”表示方法名。ClassName前面要加上但單下划線()前綴。
對於上面的代碼,Python編譯器會將“method2”方法更名為“_Personmethod2”,所以在類的外部調用“method2”方法會拋出異常。拋出異常的原因並不是“method2”方法被私有化了,而是Python編譯器把“method2”的名稱改為“_Personmethod2”了。當我們了解了這些背后的原理,就可以通過調用“_Personmethod2”方法來執行“method2”方法。
1 p = Person() 2 p._Person__method2() # 正常調用“__method2”方法
本例會創建一個MyClass類,並定義兩個公共的方法(getName和setName)和一個私有的方法(outName)。然后創建了MyClass類的實例,並調用了這些方法。為了證明Python編譯器在編譯MyClass類時做了手腳,本例還使用了inspect模塊中的getmembers函數獲取MyClass類中所有的成員方法,並輸出方法名。很顯然,“outName”被改成了“_MyClass__outName”。
1 class MyClass: 2 # 公共方法 3 def getName(self): 4 return self.name 5 # 公共方法 6 def setName(self, name): 7 self.name = name 8 # 在類的內部可以直接調用私有方法 9 self.__outName() 10 # 私有方法 11 def __outName(self): 12 print("Name = {}".format(self.name)) 13 14 myClass = MyClass() 15 # 導入inspect模塊 16 import inspect 17 # 獲取MyClass類中所有的方法 18 methods = inspect.getmembers(myClass, predicate=inspect.ismethod) 19 print(methods) 20 # 輸出類方法的名稱 21 for method in methods: 22 print(method[0]) 23 print("------------") 24 # 調用setName方法 25 myClass.setName("Bill") 26 # 調用getName方法 27 print(myClass.getName()) 28 # 調用“__outName”方法,這里調用了改完名后的方法,所以可以正常執行 29 myClass._MyClass__outName() 30 # 拋出異常,因為“__outName”方法在MyClass類中並不存在 31 print(myClass.__outName())
程序運行結果如下圖所示。
從getmembers函數列出的MyClass類方法的名字可以看出,“_MyClassoutName”被綁定到了“outName”方法上,我們可以將“_MyClassoutName”看做是“outName”的一個別名,一旦為某個方法起了別名,那么原來的名字在類外部就不可用了。MyClass類中的getName方法和setName方法的別名和原始方法名相同,所以在外部可以直接調用getName和setName方法。