Python OOP物件導向設計的類型方法(Instance, Class, Static Method)

Sean Yeh
Python Everywhere -from Beginner to Advanced
10 min readDec 24, 2021

--

The Peninsula Manila, Philippines, photo by Sean Yeh

基於讓程式碼便於閱讀與大量生產與維護之故,大家會採用物件導向(Object-oriented )的方式來建構程式。在物件導向的程式設計上,我們知道類別(class)是物件實體的設計藍圖。我們會把物件的屬性(attribute)與方法(method)都寫入這個藍圖裡面。在一個類別裡面放入很多的方法,每個方法都是一個函式,且函式裡面,又可以包含多個處理程序。此外,除了方法外,類別裡面還容納了很多的屬性。

有哪些類型方法

在進行 Python 物件導向設計時,會使用到下面幾總方法:

  • 實體方法(Instance Method)
  • 靜態方法(Static Method)
  • 類別方法(Class Method)
  • 抽象方法(Abstract Method)

其中以前三種方法(Instance,Class,Static Method)非常重要,(第四種抽象方法留待未來另行撰文說明)以下分別說明之:

Instance Method實體方法

當我們在學習建立物件時,建立的第一個class物件,採用的應該就是實體方法(Instance Method)。

實體方法,是我們大部分時候會使用到的方法,它必須要有self參數作為第一個參數。當然,我們可以在self後面傳遞其他的參數,只不過 self 必須是第一個參數。

self參數會指向物件本身,透過它可以自由的存取物件的屬性(attribute)及其他方法(method),藉此來改變物件本身的狀態。

# 類別
class Person:
# 實體方法
def walk(self):
print("走路")

實體方法的使用方式

大家可以看到上面的Person類別。我們建立了一個「人」的類別,在人這個類別裡面,我們設定instance_method 方法,這個方法即為實體方法,會傳遞物件本身作為第一個參數。我們可以對Person類別進行下面的操作。

class Person:    # 實體方法
def instance_method(self):
print(f'type is: {type(self)} \\nid is: {id(self)}')
return self
John = Person()
John.instance_method()

會得到下面的結果:

實體方法透過self參數可以自由的存取物件的屬性及其他方法,藉此來改變物件的狀態,我們將上面的Person類別修改如下:

# 類別
class Person:
#屬性
def __init__(self):
self.last_name = 'Lennon'
# 實體方法
def walk(self):
print(f"{self.last_name} 正在走路")
John = Person()
John.walk()

這時候透過 John.walk(),就可以存取物件的 last_name 屬性 Lennon 。因此,執行結果會是 Lennon 正在走路

另外,在實體方法中,我們可以透過 self.__class__ 屬性來存取或改變類別Class本身的狀態。

# 類別
class Person:
#類別變數
meals = 3
#屬性
def __init__(self):
self.last_name = 'Lennon'
# 實體方法
def walk(self):
print(f"{self.last_name} 正在走路")
def get_fat(self):
self.__class__.meals = 4
John = Person()
John.get_fat()
print(f"每天吃 {Person.meals} 餐")

以上面的程式碼來說,我們在自訂的 get_fat 方法裡面透過 self.__class__ 屬性來改變類別中 meals 的狀態。因此,程式的執行結果會是 『每天吃 4 餐』。從這個角度來看,實體方法的權限實在是很大。

Static Method 靜態方法

在一般的情況下,我們需要先透過類別實體化建立物件後,才可以呼叫方法。但是,靜態方法提供我們在沒有建立實體化物件的情況下也可以呼叫它。

使用靜態方法需要透過修飾器 @staticmethod 。另一個與一般實體方法不同的是,靜態方法不會傳遞物件本身(self)作為第一個參數,它只能處理使用者傳入的參數。

靜態方法的使用方式

以下面的例子來說,run為靜態方法。我們在def run的上方加上修飾器 @staticmethod ,並且在run的後面放置一個沒有self的括號。

# 類別
class Person:
# 靜態方法
@staticmethod
def run():
print("跑步吧!")
John = Person()
John.run()

當然,靜態方法雖不需要self,卻可以接受其他任意的參數。也因此靜態方法無法改變類別(Class)以及物件的狀態。

# 類別
class Person:
# 靜態方法
@staticmethod
def run(time,distance):
print(f"{time} 跑了{distance} 公尺!")
John = Person()
John.run("今天",5000)

由於靜態方法無法改變類別(Class)以及物件的狀態,因此通常會使用在不需要存取物件屬性或方法的時候。例如上面的程式碼,我們將靜態方法run進行修改,加入兩個參數 timedistance。 執行程式後會得到「今天 跑了5000 公尺!」的結果。

Class Method 類別方法

類別方法一樣提供我們在沒有建立實例化物件的情況下可以呼叫它。但與靜態方法不同的地方是,類別方法會傳遞類別本身(cls)作為第一個參數。

使用類別方法需要透過修飾器 @classmethod

class Person:
@classmethod
def aclassmethod(cls):
print(cls)
print(f'type is: {type(cls)} \\nid is: {id(cls)}')
Person.aclassmethod()

會得到下面的結果:

不同於實體方法的self參數會指向物件本身,類別方法的cls參數,會指向類別(Class)本身。因此,類別方法不能修改以該類別建立出來的物件,類別方法只能夠改變類別(Class)本身的狀態,在使用上要特別注意。

雖說類別方法無法直接改變物件的狀態,但實際上類別方法可以透過改變類別(Class)本身的狀態,進而間接的影響該類別建立出來物件。

類別方法有能力存取類別的狀態或方法。另外,類別方法也可以做為操控類別變數(class variable)與做為建構子(Constructor)的替代與補充。

類別方法的使用方式

例如,假設我們有一家手搖飲品店,需要製作各種不同品項的飲料。這時候可以拿類別方法當成工廠函式(factory function)來生產出各種不同的品項:

class Drinks:
def __init__(self,name,materials):
self.name = name
self.materials = materials

def __repr__(self):
return f'{str(self.name)} 材料:{str(self.materials)}'
#return f'Drinks({self.materials})'

@classmethod
def milk_tea(cls):
return cls('奶茶',['牛奶','紅茶'])
@classmethod
def pearl_milk_tea(cls):
return cls('珍珠奶茶',['牛奶','紅茶','珍珠'])

@classmethod
def jasmine_green_tea(cls):
return cls('茉莉綠茶',['茉莉花','綠茶'])

#印出成分
print(Drinks.milk_tea())
print(Drinks.pearl_milk_tea())
print(Drinks.jasmine_green_tea())

在上面的程式裡,我們在milk_teapearl_milk_teajasmine_green_tea方法裡面使用的是 cls 參數,而不是一般常用的 self 參數。執行結果如下:

使用這種方式,我們可以依照市場需要隨時增加特定口味的飲料品項而不需要「回過頭」去修改類別的屬性,好處是程式碼會比較 『乾』(DRY = Don’t Repeat Yourself)。

例:合併使用類別方法與靜態方法

下面的例子合併使用了類別方法與靜態方法。類別Person有兩個屬性name(姓名)與age(年齡)。我們藉由讓使用者輸入自己的姓名與出生日期,給程式計算使用者的年齡並且判斷該年齡是否已成年後,最後透過print顯示在螢幕上。

from datetime import date# 類別
class Person:
#屬性
def __init__(self,name,age):
self.name = name
self.age= age
# 類別方法
@classmethod
def birth_year(cls, name, b_year):
age = date.today().year - b_year
return cls(name,age)

#靜態方法
@staticmethod
def isAdult(age):
if age >= 18:
return '已成年'
else:
return '未成年'
John = Person.birth_year('John', 2003)
print(f'{John.name},{John.age}歲 {Person.isAdult(John.age)}')

執行後會得到下面的結果:

透過這種方式,我們可以未來可以重複使用 birth_year 方法,來將年轉換為歲數,而isAdult方法,則可以用來判斷某個數字是否大於等於18。這樣子處理是不是覺得比較簡潔方便?

self 與 cls

值得一提的是,在習慣上我們會以self作為命名,代表物件本身的參數。而會以cls作為命名,代表類別本身的參數。若您想使用別的名稱作為代表,並非不可,只要它們必須當成物件與類別的第一個參數。只不過如此一來其他工程師恐怕較難理解您的程式碼。

結語

綜合上述,實體方法(Instance Method)可以存取物件實體,是最常用的方法。類別方法(Class Method)可以存取類別,可以呼叫類別本身,用來製作替代的建構子。而靜態方法(Static Method)無法存取物件與類別,它獨立於物件或類別以外。使用靜態方法與類別方法可以讓我們清楚的區分出類別的架構中「可以改變」與「不可以改變」的範圍。避免在無意之間越過了界線,產生不容易找不到的程式臭蟲(bug)。

只要能夠明白這些方法的差異,在撰寫Python物件導向(Object-oriented Python )程式的時候,就可以清楚的表達目的。程式碼也會比較容易維護。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。