之前的文章我們簡單介紹了一下 Python 的函式,本篇文章我們來看一下 Python 中的物件導向。
Python從設計之初就已經是一門物件導向的語言,正因為如此,在Python中建立一個類和物件是很容易的。
物件導向技術簡介
- 類(Class): 用來描述具有相同的屬性和方法的物件的集合。它定義了該集合中每個物件所共有的屬性和方法。物件是類的例項。
- 方法:類中定義的函式。
- 類變數:類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。類變數通常不作為例項變數使用。
- 資料成員:類變數或者例項變數用於處理類及其例項物件的相關的資料。
- 方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。
- 區域性變數:定義在方法中的變數,只作用於當前例項的類。
- 例項變數:在類的宣告中,屬性是用變數來表示的。這種變數就稱為例項變數,是在類宣告的內部但是在類的其他成員方法之外宣告的。
- 繼承:即一個派生類(derived class)繼承基類(base class)的欄位和方法。繼承也允許把一個派生類的物件作為一個基類物件對待。例如,有這樣一個設計:一個Dog型別的物件派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
- 例項化:建立一個類的例項,類的具體物件。
- 物件:通過類定義的資料結構例項。物件包括兩個資料成員(類變數和例項變數)和方法。
和其它程式語言相比,Python 在儘可能不增加新的語法和語義的情況下加入了類機制。
Python中的類提供了物件導向程式設計的所有基本功能:類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以呼叫基類中的同名方法。
物件可以包含任意數量和型別的資料。
Python 中定義類的方法是以 class k開頭,我們先定義一個類:
1 class Cat: 2 def eat(self): 3 print("貓吃魚") 4 5 cat = Cat() 6 cat.eat() # 貓吃魚
如上 ,我們定義了一個 Cat 類,裡面定義了一個方法,我們可以通過 變數=類名() 的方式來例項化一個類,這樣我們就可以呼叫類裡面的屬性和方法,所以當我們呼叫 cat.eat() 時輸出列印 Cat 類裡面的 eat() 方法。
在上面的 eat() 方法中,我們預設穿了一個 self 的引數,它代表該類的例項,不一定非叫 self ,可以叫 xxx,只是我們約定俗成叫 self,跟 *args 和 **kwargs 一個道理,那該如何理解這個 self 呢,如下:
1 class Cat: 2 def eat(self): 3 print(self.name+"吃魚") 4 5 6 tom = Cat() 7 tom.name = "Tom" 8 tom.eat() # Tom吃魚 9 10 jerry = Cat() 11 jerry.name = "Jerry" 12 jerry.eat() # Jerry吃魚
在上面的程式碼中,我們定義了兩個 Cat 的例項,並且在每個例項中都新增了一個 name 屬性,如果我們想要在 eat() 方法中輸出各自定義的 name 值,我們可以通過 self 屬性來定義,因為 self 就表示該類的例項,所以就會去拿各自例項裡面的 name 值。
上面的程式碼中我們可以實現輸出不同的 name 值,但是需要我們在例項化後自己定義 name,我們也可以在定義類的時候就將 name 值傳入來做:
1 class Cat: 2 def __init__(self,name): 3 self.name = name 4 def eat(self): 5 print(self.name+"吃魚") 6 7 tom = Cat("Tom") 8 tom.eat() # Tom吃魚 9 10 jerry = Cat("Jerry") 11 jerry.eat() # Jerry吃魚
上面的程式碼中,我們在 Cat 類中定義了一個 __init__() 的方法,該方法是類自帶的方法,第一個引數必須為 self,我們可以在裡面定義自己所需的變數。如上,我們在例項化 Cat 類的時候就將 Tom ,Jeery 傳入,然後在 self 形參後面新增形參,該形參與傳入引數的順序意義對應,這樣我們就可以使用傳入的引數了,定義和使用時需要在前面加 self.
在上面的程式碼演示中,我們可以看出當我們例項化一個類之後,就能呼叫該類的方法,這種方法叫公有方法,還有一種方法叫私有方法,就是例項化後不能被呼叫,只有自己內部可以呼叫,如下:
1 class Cat: 2 def __init__(self,name): 3 self.name= name 4 def eat(self): 5 print(self.name+"吃魚") 6 self.__run() 7 def __run(self): 8 print("私有方法") 9 10 tom = Cat("Tom") 11 tom.eat() # Tom吃魚 私有方法 12 tom.__run() # AttributeError: 'Cat' object has no attribute '__run'
上面的程式碼中當我們例項化 tom 後,可以通過 eat() 方法呼叫到 __run() 方法,但是直接呼叫 __run() 會報錯。
不僅私有方法不能被呼叫,私有屬性也不能,如下:
1 class Cat: 2 age = 11 3 __height = 120 4 def add(self): 5 self.age += 1 6 self.__height += 1 7 8 tom = Cat() 9 tom.add() 10 print(tom.age) # 12 11 print(tom.__height) # AttributeError: 'Cat' object has no attribute '__height'
接下來我們看一下類中的繼承,在上面的程式碼中我們定義了一個 Cat 的類,裡面有一個 eat() 方法,當我們定義一個 Dog 類時,它也有一個 eat() 方法,而且 eat() 方法是一樣的,現在 Cat 類有一個 bark() 方法,Dog 類裡也有一個 bark() 方法,但是這兩個方法執行的結果不一樣,而且 Dog 類裡有一個 swim() 方法,而 Cat 裡沒有,如果我們都分別定義這兩個類的話,程式碼會很長,而且如果兩個相同的 eat() 方法需要修改時需要修改兩個地方,這時我們可以用繼承的方式解決,如下:
1 class Animal: 2 def eat(self): 3 print("吃吃吃") 4 5 class Cat(Animal): 6 def bark(self): 7 print("喵喵喵") 8 9 class Dog(Animal): 10 def bark(self): 11 print("汪汪汪") 12 13 def swim(self): 14 print("狗刨式") 15 16 cat = Cat() 17 cat.eat() # 吃吃吃 18 cat.bark() # 喵喵喵 19 cat.swim() # AttributeError: 'Cat' object has no attribute 'swim' 20 21 dog = Dog() 22 dog.eat() # 吃吃吃 23 dog.bark() # 汪汪汪 24 dog.swim() # 狗刨式
我們將 Cat 類和 Dog 類相同的 eat() 方法定義在了 Animal 類裡,然後在建立 Cat 和 Dog 類時新增了 (Animal),意思是繼承 Animal 類,這樣我們在例項化 Cat 和 Dog 類後就能呼叫 Animal 類裡的方法 eat(),而且還能呼叫各自例項裡的 bark() 方法,但是如果沒有另一個類,則不能使用該類的方法,如 cat 呼叫 dog 的 swim() 方法就會報錯。
當然我們可以讓 Cat 類也繼承 Dog 類,如下:
1 class Animal: 2 def eat(self): 3 print("吃吃吃") 4 # Cat 類要想繼承 Dog 類必須寫在 Dog 類後面 5 # class Cat(Dog,Animal): 6 # def bark(self): 7 # print("喵喵喵") 8 9 class Dog(Animal): 10 def bark(self): 11 print("汪汪汪") 12 def swim(self): 13 print("狗刨式") 14 15 class Cat(Dog,Animal): 16 def bark(self): 17 print("喵喵喵") 18 19 cat = Cat() 20 cat.eat() # 吃吃吃 21 cat.bark() # 喵喵喵 22 cat.swim() # 狗刨式 23 24 dog = Dog() 25 dog.eat() # 吃吃吃 26 dog.bark() # 汪汪汪 27 dog.swim() # 狗刨式
如上,我們可以讓 Cat 同時繼承 Dog 和 Animal 兩個類,但是如若想要繼承某類,必須先建立該類。
我們也可以多重繼承,即讓 Animal 類也繼承某類,這樣 Cat 和 Dog 如果繼承了 Animal 類,那麼也可以使用 Animal 類繼承的父類的屬性和方法。
只得注意的是私有屬性和方法不能被繼承,因為私有屬性和方法只能在自己的類中使用。
接下來我們看一下類中方法的重寫:
1 class Animal: 2 def eat(self): 3 print("吃吃吃") 4 5 class Cat(Animal): 6 pass 7 8 class Dog(Animal): 9 def eat(self): 10 print("大口大口吃") 11 12 cat = Cat() 13 cat.eat() # 吃吃吃 14 15 dog = Dog() 16 dog.eat() # 大口大口吃
在上面的程式碼中,我們讓 Cat 和 Dog 類都繼承了 Animal 類,但是在 Dog 類中,我們定義了一個和 Animal 類中一樣的方法名,但是執行的結果不一樣,當我們分別呼叫例項化的 cat 和 dog 的 eat() 方法時,Cat 類由於沒有自己的 eat() 方法,所以向上尋找,發現繼承的 Animal 類中有 eat() 方法,所以就呼叫了 Animal 類中的 eat() 方法,但是 Dog 類中有 eat() 方法,所以就呼叫自己的 eat() 方法,不在向上尋找,這就是類中方法的重寫。
在上面我們說過了類屬性和例項方法,接下來我們來看一下類方法:
1 class Cat: 2 # 類屬性 3 age = 10 4 5 # 例項方法 6 def __init__(self): 7 self.name = "Tom" 8 9 def info(self): 10 print(self.name, self.age) 11 12 # 類方法 13 @classmethod 14 def addAge(cls): 15 cls.age = 22 16 17 tom = Cat() 18 tom.info() # Tom 10 19 Cat.info() # TypeError: info() missing 1 required positional argument: 'self' 20 21 tom.addAge() 22 print(tom.age) # 22 23 print(Cat.age) # 22 24 25 Cat.addAge() 26 print(tom.age) # 22 27 print(Cat.age) # 22
如果我們在類裡面的方法前面加 @classmethod,就表明該方法為類方法,在說類方法前我們再來看一下例項方法。
當我們例項化一個 tom 後,我們就可以呼叫 tom 裡面的例項方法,我們之前說過 self 指的是該類的例項化,所以當我們呼叫 tom.info() 時能正常呼叫,但是 Cat.info() 時則會報錯,因為裡面的 self 指向的是例項化的 tom,而不是 Cat 類,所以會報錯。
在類方法中,我們同樣謠傳一個預設的形參,預設叫 cls,它指向的是類本身,而不是該類的例項化,所以我們可以通過 cls.age=22 來更改類裡面的類屬性,而且類方法 addAge() 可以使用例項化的 tom 來呼叫,也可以使用 Cat 類本身來呼叫。