今天要來看 Python 的物件導向,其實我在大二上的物件導向課的內容都快忘光光了,所以這個篇文章可能不太詳細。
什麼是物件
物件裡面有 data (通常為變數,稱為屬性) 與 方法or動作 (通常為 function),代表一個具體的東西。
其實我覺得很難解釋和表達這個名詞,我是這麼想的,就像我,我是一個人,人一定有身高體重,然後人會走路和跳舞等等的動作,我就是一個物件的概念。身高體重就是 data,動作就是 function。那 class 就是定義人這個名詞,則是比較抽象的。
或者像是 Python 裡面 String 這個變數型態就是一個 class,而我們創建的字串(str(96))就是一個物件,它的屬性就是字串內容('96')和 type ,而方法就是它裡面內建的 function。
來看 code:1class Person():2 pass34me = Person()
我們定義一個 class 叫做 Person,我們用 me = Person() 來建立一個 Person 物件叫做 me。
把玩物件
我們來實作個,題目為計算 BMI 好了。我們建立一個 class 叫做 Human,裡面有身高體重兩個屬性,並且有兩個方法一個是初始化,一個是計算 BMI
首先要有身高體重1class Human():2 def __init__(self,height = 0,weight = 0):3 self.height = height4 self.weight = weight56me = Human(height =180,weight =70)
以上 code 就是 class 的定義方式,而比較特別的地方是 __init__( ) 和 self :
__init__( ): 是特殊的 Python 名稱,是用來初始化一個物件。self: 是指他 reference 至自己本身這個物件,就是 self 代表這個物件本身。java, c++ 中的 this 是差不多的東西。
我們來看me = Human(height =180,weight =70)這行所作的事情:
- 查看
Humanclass 的定義 - 在記憶體中建立(實體)一個物件
- call 該物件的
__init__,並把物件給self。 - 在物件中儲存引數內容
- return 物件,並將
me指定給這個物件
我們定義好身高體重了,接下來是加入 BMI 測量的 function:1class Human():2 def __init__(self,height=0, weight=0):3 self.height = height4 self.weight = weight56 def compute_BMI(self):7 return self.weight/((self.height/100)**2)89def main():10 me = Human('Jack', height=180, weight=70)1112 BMI = me.compute_BMI() # 使用物件裡的 compute_BMI( )13 print(BMI)1415if __name__ == '__main__':16 main()17---------------執行結果---------------1821.604938271604937
繼承
這時我們需要一個叫做 Woman 的 class ,裡面包括身高體重、三圍,然後計算 BMI 和 顯示三圍。
依照這個想法我們會這樣寫:
1 | class Woman(): |
2 | def __init__(self,height=0, weight=0,bust=0,waist=0,hip=0): |
3 | self.height = height |
4 | self.weight = weight |
5 | self.bust = bust |
6 | self.waist = waist |
7 | self.hip = hip |
8 | |
9 | def compute_BMI(self): |
10 | return self.weight/((self.height/100)**2) |
11 | |
12 | def print_BWH(self): |
13 | print('B:{},W:{},H:{}'.format(self.bust,self.waist,self.hip)) |
14 | |
15 | |
16 | def main(): |
17 | me = Woman(height=180, weight=70,bust=83,waist=64,hip=83) |
18 | |
19 | BMI = me.compute_BMI() |
20 | me.print_BWH() |
21 | print(BMI) |
22 | |
23 | if __name__ == '__main__': |
24 | main() |
25 | |
26 | ---------------執行結果--------------- |
27 | B:83,W:64,H:83 |
28 | 21.604938271604937 |
我們會發現有些部分跟 Human( )有 87% 像,有些根本就是複製貼上,好像很方便。
不過如果是大程式呢,一次就幾千行程式,這樣複製貼上重複的 code 那不就更多了,在管理程式上是一大的麻煩,而且以後出社會工作並不會一個人幹完整個 project ,一定是團隊合作,如果我們寫出這種 code ,對其他組員來說是一大麻煩,所以我們需要用繼承的方式來減少重複的 code,不要當個雷組員。
1 | class Human(): |
2 | def __init__(self,height=0, weight=0): |
3 | self.height = height |
4 | self.weight = weight |
5 | |
6 | def compute_BMI(self): |
7 | return self.weight/((self.height/100)**2) |
8 | |
9 | class Woman(Human): |
10 | def __init__(self,height=0, weight=0,bust=0,waist=0,hip=0): |
11 | super().__init__(height=height,weight=weight) |
12 | self.bust = bust |
13 | self.waist = waist |
14 | self.hip = hip |
15 | |
16 | def print_BWH(self): |
17 | print('B:{},W:{},H:{}'.format(self.bust,self.waist,self.hip)) |
在 Woman的( ) 裡填上 Human 就繼承了
我們來看經過繼承後的 Woman( ):
- 我們使用
super( )來 callHuman( )的初始化,這樣我們就不用再寫一次一樣的code。 - 我們也不用再寫
def compute_BMI(self)的定義了,因為Woman( )已經繼承了,就像你爸爸把公司交給你,你不需要再重新打造這個公司,直接接手。
覆寫
假如我們有一個 class 叫做 Human( ) ,而裡面的功能只有 talk( ) 用來說我是人類。
這時我們有一個 Woman( ) 繼承 Human( ),但是我們想要 talk( ) 用來輸出我是女人,這件事是可以實現的。只要重新定義就行了。
1 | class Human(): |
2 | def talk(self): |
3 | print('I am Human!') |
4 | |
5 | class Woman(Human): |
6 | def talk(self): |
7 | print("I am Woman!") |
8 | |
9 | def main(): |
10 | human = Human() |
11 | woman = Woman() |
12 | |
13 | human.talk() |
14 | woman.talk() |
15 | |
16 | if __name__ == '__main__': |
17 | main() |
18 | ---------------執行結果--------------- |
19 | I am Human! |
20 | I am Woman! |
私有屬性和方法
為了要讓城市有封裝性,有些語言像是 C++ 會設定 public 和 private 來保護資料,然後需要 get( ) 或 set( ) 來存取。
Python 並不這麼麻煩! 因為所有的屬性和方法都是共用的(public),但如果不想讓外部存取,我們可以在屬性和方法的命名前加上__
那怎麼使用呢?
來實做個,我們有一個 class 叫做 Duck( ),他只有一個屬性叫做 __name ,這個是我們不希望有人直接存取他,有點像 C++ private 的概念。
1 | class Duck(): |
2 | def __init__(self,input_name): |
3 | self.__name = input_name |
4 | |
5 | def print_name(self): |
6 | print(self.__name) |
7 | |
8 | def main(): |
9 | fowl = Duck('Mike') |
10 | print(fowl.__name) # 印看看 __name |
11 | |
12 | if __name__ == '__main__': |
13 | main() |
14 | ---------------執行結果--------------- |
15 | print(fowl.__name) |
16 | AttributeError: 'Duck' object has no attribute '__name' |
我們成功的不能印出__name,但其實並不是把變數變成私有化,而是打亂了他的名稱,如果我們使用 _Duck__name 是否能成功 print 出
1 | print(fowl._Duck__name) |
2 | ---------------執行結果--------------- |
3 | Mike |
所以算是變向私有化XD
看完取,我來看看存
1 | def main(): |
2 | fowl = Duck('Mike') |
3 | fowl.__name = 'Jojo' |
4 | print(fowl.__name) |
5 | ---------------執行結果--------------- |
6 | Jojo |
咦? 奇怪問什麼 __name 被改變了
不急我們用方法 print_name( ) 和 print(fowl._Duck__name)看看。1def main():2 fowl = Duck('Mike')3 fowl.__name = 'Jojo'4 fowl.print_name()5 print(fowl._Duck__name)6---------------執行結果---------------7Mike8Mike
诶? 還是原本的
因為 Duck 的 __name 在外部叫做 _Duck__name 而不是 __name,所以 fowl.__name 是新的變數。
Instance method & Class method & Static Method
在之前說到, self 為參考自己的物件,是一個實例方法(Instance method)。當我們 call 方法時,Python 會把物件交給 self。
什麼意思?
1 | class Number(): |
2 | def __init__(self,input_number): |
3 | self.number = input_number |
4 | def print_number(self): |
5 | print('This is',self.number) |
6 | |
7 | example = Number(10) |
8 | example.print_number() |
example = Number(10)example.print_number()
我們可以看成:example = Number(10)Number.print_number(example,10)
在Number.print_number(example,10)中,example 所參考的實例會交給 self,讓電腦知道你要用哪一個。
為什麼要這樣?
假如我們今天設置了很多個 Number( )的物件卻不使用self會發生什麼事?1class Number():2 def __init__(input_number):3 number = input_number4 def print_number():5 print('This is',input_number)67example = Number(10)8example_2 = Number(50)9example_3 = Number(100)1011example.print_number()12example_2.print_number()13example_3.print_number()14---------------執行結果---------------15 example = Number(10)16TypeError: __init__() takes 1 positional argument but 2 were given
由於 Python 不知道是誰要使用__init__( )所以他報錯了。
那如果我們不要使用self來指實例呢?
我們可以用 @classmethod 裝飾器(decrator)來說明接下來要使用Class method。
Class Method 會影響整個 class 也就是說你任何改變,會影響每一個物件。
但是第一個參數是 class 本身,也就是 self 改成 cls
1 | class Number(): |
2 | count = 0 # 計算 object 有幾個 |
3 | def __init__(self): |
4 | Number.count += 1 |
5 | def talk(self): |
6 | print('I am a Number!!') |
7 | |
8 | |
9 | def print_kids(cls): |
10 | print('Number has',cls.count,'little object') |
11 | |
12 | no1 = Number() |
13 | no2 = Number() |
14 | no2 = Number() |
15 | |
16 | Number.print_kids() |
17 | ---------------執行結果--------------- |
18 | Number has 3 little object |
但是他會影響其他的 object,有沒有辦法不影響?
我們可以用 Static Method,以 staticmethod 的 decorator 做開頭,不需要 self 和 cls
1 | class Human(): |
2 | |
3 | def talk(): |
4 | print('I am a human') |
5 | |
6 | |
7 | Human.talk() |
8 | ---------------執行結果--------------- |
9 | I am a human |