今天要來看 Python 的物件導向,其實我在大二上的物件導向課的內容都快忘光光了,所以這個篇文章可能不太詳細。
什麼是物件
物件裡面有 data (通常為變數,稱為屬性) 與 方法or動作 (通常為 function),代表一個具體的東西。
其實我覺得很難解釋和表達這個名詞,我是這麼想的,就像我,我是一個人,人一定有身高體重,然後人會走路和跳舞等等的動作,我就是一個物件的概念。身高體重就是 data,動作就是 function。那 class 就是定義人這個名詞,則是比較抽象的。
或者像是 Python 裡面 String
這個變數型態就是一個 class
,而我們創建的字串(str(96)
)就是一個物件,它的屬性就是字串內容('96'
)和 type ,而方法就是它裡面內建的 function。
來看 code:1
class Person():
2
pass
3
4
me = Person()
我們定義一個 class 叫做 Person,我們用 me = Person()
來建立一個 Person 物件叫做 me。
把玩物件
我們來實作個,題目為計算 BMI 好了。我們建立一個 class
叫做 Human,裡面有身高體重兩個屬性,並且有兩個方法一個是初始化,一個是計算 BMI
首先要有身高體重1
class Human():
2
def __init__(self,height = 0,weight = 0):
3
self.height = height
4
self.weight = weight
5
6
me = Human(height =180,weight =70)
以上 code 就是 class 的定義方式,而比較特別的地方是 __init__( )
和 self
:
__init__( )
: 是特殊的 Python 名稱,是用來初始化一個物件。self
: 是指他 reference 至自己本身這個物件,就是 self 代表這個物件本身。java, c++ 中的 this 是差不多的東西。
我們來看me = Human(height =180,weight =70)
這行所作的事情:
- 查看
Human
class 的定義 - 在記憶體中建立(實體)一個物件
- call 該物件的
__init__
,並把物件給self
。 - 在物件中儲存引數內容
- return 物件,並將
me
指定給這個物件
我們定義好身高體重了,接下來是加入 BMI 測量的 function: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
def main():
10
me = Human('Jack', height=180, weight=70)
11
12
BMI = me.compute_BMI() # 使用物件裡的 compute_BMI( )
13
print(BMI)
14
15
if __name__ == '__main__':
16
main()
17
---------------執行結果---------------
18
21.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)
看看。1
def main():
2
fowl = Duck('Mike')
3
fowl.__name = 'Jojo'
4
fowl.print_name()
5
print(fowl._Duck__name)
6
---------------執行結果---------------
7
Mike
8
Mike
诶? 還是原本的
因為 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會發生什麼事?1
class Number():
2
def __init__(input_number):
3
number = input_number
4
def print_number():
5
print('This is',input_number)
6
7
example = Number(10)
8
example_2 = Number(50)
9
example_3 = Number(100)
10
11
example.print_number()
12
example_2.print_number()
13
example_3.print_number()
14
---------------執行結果---------------
15
example = Number(10)
16
TypeError: __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 |