python面向物件筆記
一、封裝(屬性/私有方法/公有方法/靜態方法/建構函式...)
# 定義一個類 class Animal: # 私有成員(用_開頭的約定為私有成員 - 注:僅僅是君子協定) _age = 0 # 建構函式 def __init__(self, name): # 建議所有私有成員在這裡初始化(不管是已定義的,還是"動態"新增的) self.name = name # 動態新增的私有成員 self._age = 1 # 已經定義好的私有成員 # 靜態方法 @staticmethod def eat(): print("Animal can eat food") # 公有方法 def to_string(self): return "name:" + self.name # 私有方法(用_開頭的約定為私有方法 - 注:僅僅是君子協定) def _some_method(self): return "this is a private method of :" + self.name # setter示例 def set_age(self, age): if age < 0 or age > 100: print("age is invalid!") self._age = age # getter示例 def get_age(self): return self._age def run(self): print(self._some_method()) print("I am running") def test(self): print("test1") # 注:這樣會覆蓋上面的test(self版本) def test(self, hello): print("test2:" + hello) animal = Animal("A new animal") print(animal.to_string()) # 方法呼叫 animal.eat() # 靜態方法呼叫1(不推薦) Animal.eat() # 靜態方法呼叫2 print("animal._age=" + str(animal.get_age())) animal.set_age(10) # 呼叫setter print(animal.get_age()) # 呼叫getter,輸出10 animal.run() # 公有方法裡呼叫私有方法 animal._age = 30 # 直接修改私有成員(不推薦) animal._age2 = 40 # 注意:這裡實際上給例項animal"動態"添加了一個_age2屬性 print(animal.get_age()) # 這裡輸出的仍然是10 print("_age2:" + str(animal._age2)) # 這裡輸出的動態新增的_age2屬性值 print(animal.test("hi")) #print(animal.test()) # 這裡會報錯,因為test(self,hello)這個版本要求hello引數有值 print(animal._some_method()) # 直接呼叫私有方法(不推薦)
輸出:
name:A new animal Animal can eat food Animal can eat food animal._age=1 10 this is a private method of :A new animal I am running 30 _age2:40 test2:hi None this is a private method of :A new animal
幾個要點:
1、約定大於配置:比如建構函式約定為__init__;私有方法/成員約定為"_"開頭(注:只是君子協定,硬要呼叫的話,外部是可以直接呼叫的);例項方法約定第1個引數為self
2、過載的處理,不象java裡要定義多個方法簽名,python裡就一個版本,但是通過可變引數來實現(後面還要細講)
3、動態語言,例項在執行過程中,可隨時動態新增屬性
另外注意:不要輕易使用__(二個連續的下劃線)做為方法或變數的字首或字尾,"__XXX__"在python裡通常有特定含義,如果使用"__"開頭的變數或寫法,可能會遇到各種奇葩問題。
二、過載
傳統的OOP語言,比如java,過載只能是定義多個不同方法簽名的method,來實現過載,但是python可以做得更簡單,用引數預設值就可以變相實現。
# 定義一個類 class Calculator: # 加法 def add(self, a=0, b=0, c=0, d=0): print("args:", a, b, c, d) return int(a) + int(b) + int(c) + int(d) cal = Calculator() print(cal.add(1, 2)) print(cal.add(1, 2, 3)) print(cal.add(1, "2", 3, "4")) print("\n") cal = Calculator #注意這裡,如果漏了(),結果會大不同 print(cal.add(1, 2))
輸出:
args: 1 2 0 0 3 args: 1 2 3 0 6 args: 1 2 3 4 10 args: 2 0 0 0 2
注意:16行,如果漏了(),cal = Calculator,實際上只是cal只是相當於類Calculator的別名,這樣呼叫add(1,2)時,類似於靜態方法呼叫,1會認為是第一個引數self,所以輸出就成了2.
如果不確定引數個數,還可以這麼做:
# 定義一個類 class Calculator: # 約定:*引數,表示引數個數不限定 def add(self, *args): result = 0 for arg in args: result += int(arg) return result cal = Calculator() print(cal.add(1, 2, 3)) print(cal.add("1", 2, "3"))
輸出:
6 6
三、繼承
3.1 基本示例
class Fruit: def __init__(self, name): print("Fruit constructor...") self.name = name def to_string(self): print("Fruit to_string...") return "name:" + self.name # 抽象方法 def get_color(self): print("Fruit get_color...") raise NotImplementedError class RedApple(Fruit): def __init__(self, name): print("Apple constructor...") # 呼叫父類的建構函式 Fruit.__init__(self, name) def get_color(self): return self.name + " is red" fruit = Fruit("unknown") print(fruit.to_string()) # print(fruit.get_color()) # 報錯,因為沒實現 print("\n") redApple = RedApple("red apple") print(redApple.get_color()) print(redApple.to_string()) print("\n") print("1、redApple is instance of RedApple ? ", isinstance(redApple, RedApple)) print("2、redApple is instance of Fruit ? ", isinstance(redApple, Fruit)) print("3、fruit is instance of Fruit ? ", isinstance(fruit, Fruit)) print("4、fruit is instance of RedApple ? ", isinstance(fruit, RedApple)) print("5、RedApple is subclass of Fruit ? ", issubclass(RedApple, Fruit)) print("6、Fruit is subclass of Fruit ? ", issubclass(Fruit, Fruit)) print("7、Fruit is subclass of RedApple ? ", issubclass(Fruit, RedApple))
輸出:
Fruit constructor... Fruit to_string... name:unknown Apple constructor... Fruit constructor... red apple is red Fruit to_string... name:red apple 1、redApple is instance of RedApple ? True 2、redApple is instance of Fruit ? True 3、fruit is instance of Fruit ? True 4、fruit is instance of RedApple ? False 5、RedApple is subclass of Fruit ? True 6、Fruit is subclass of Fruit ? True 7、Fruit is subclass of RedApple ? False
注:抽象方法是通過丟擲未實現的異常來實現的。如果想類似java定義抽象類,把__init__方法丟擲未實現異常就行。
3.2 多繼承
python支援多繼承,這點與java有很大區別
class P1: def a(self): print("P1-a") def b(self): print("P1-b") def x(self): print("P1-x") class P2: def a(self): print("P2-a") def b(self): print("P2-b") def y(self): print("P2-y") # 多繼承示例 class S1(P1, P2): def a(self): print("S1-a") class S2(P2, P1): def a(self): print("S2-a") s1 = S1() s1.a() s1.b() # P1-b s1.x() s1.y() print("\n") s2 = S2() s2.a() s2.b() # P2-b s2.x() s2.y() print("\n") print("s1 isinstance of P1:", isinstance(s1, P1)) print("s1 isinstance of P2:", isinstance(s1, P2)) print("s1 isinstance of S1:", isinstance(s1, S1)) print("s1 isinstance of S2:", isinstance(s1, S2))
輸出:
S1-a P1-b P1-x P2-y S2-a P2-b P1-x P2-y s1 isinstance of P1: True s1 isinstance of P2: True s1 isinstance of S1: True s1 isinstance of S2: False
注意多承繼的順序,如果“爸爸們”之間有重名方法,將會按照繼承順序,誰在前面,就呼叫誰的。eg:def S(A,B) 子類S繼承自A,B,如果A,B中都有方法x,呼叫S.x時,因為A排在B的前面,所以呼叫到的就是A.x方法。從上面的輸出就可以得到印證。
3.3 介面、abc模組、屬性
3.1中抽象類/方法是通過丟擲異常來實現的,有點粗暴,下面介紹一種更優雅的方法,python內建的abc模組
from abc import * # 介面示例 class IRun(ABC): # 抽象方法 @abstractmethod def run(self): pass class Animal(ABC): # 抽象屬性 @property @abstractmethod def name(self): pass class Dog(Animal, IRun): def __init__(self, name): self._name = name; # 屬性的getter @property def name(self): return self._name # 屬性的setter (注:每個property都會生成一個對應的@xxx.setter) @name.setter def name(self, value): self._name = value # 介面的方法實現 def run(self): print(self.name + " is running") dog1 = Dog("a") dog1.run() dog2 = Dog("b") dog2.run() print(isinstance(dog1, IRun)) print(id(dog1), id(dog2), id(dog1) == id(dog2)) # 判斷2個例項是否相等
輸出:
a is running b is running True 4314753736 4314753792 False