0%

Python 物件

今天要來看 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 :

  1. __init__( ): 是特殊的 Python 名稱,是用來初始化一個物件。
  2. 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( )來 call Human( ) 的初始化,這樣我們就不用再寫一次一樣的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
    @classmethod
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 做開頭,不需要 selfcls

1
class Human():
2
    @staticmethod
3
    def talk():
4
        print('I am a human')
5
6
7
Human.talk()
8
---------------執行結果---------------
9
I am a human

資料來源:

精通 Python:運用簡單的套件進行現代運算