Python 的 staticmethod 與 classmethod

莊子弘
7 min readDec 16, 2020

因為工作需求開始需要學習Python,在花了一天看教學的過程中發現幾點相對於其他語言比較特別的地方,順手作個筆記紀錄一下。

  1. 不支援interface,因為這個鴨子型態理論...
  2. __init__() 等價於 物件導向語言的Constructor
  3. 基本上class 裡面的function 的第一個參數都需要指向自己,但有兩個例外: staticmethod 與 classmethod

第三點比較特別一點,接下來講的都跟第三點有關:

第一個參數都需要指向自己?

class Dates:
def __init__(self, date):
self.date = date

def getDate(self):
return self.date

這個self 可以任意命名,叫做this也OK。重點是只要是在class 內的function的第一個參數都會指到自己。

指到自己是什麼意思?

其實把self 印出來就知道了

#Exampleclass A(object):
def func1(self):
print(self)
a = A()
a.func1()
#Output<__main__.A object at 0x2afb4bde9160> #這就是a物件的記憶體位置

如何定義staticmethod 與 classmethod?

很簡單,在function 前一行加上decorator 即可

@staticmethod
def func(args, ...)
@classmethod
def func(cls, args...)

staticmethod

如同大家所熟知的static method,是一種靜態函數。因為是被綁定在類別中,所以不需要建立物件即可使用。也因此他無法存取物件資料,只能對傳入的參數做處理。

什麼時候用static method 比較好?

  1. 整合工具箱的時候。例如寫一個class專門處理日期格式的轉換,就很適合用static method來處理資料的轉換。
  2. 只有單一實現的時候。例如你不希望子類別覆寫該函數,為了確保該函數不被覆寫就可加入@staticmethod 這個decorator

classmethod

如同staticmethod綁定的對象在類別上,classmethod綁定的對象也是類別,也因此這兩種函數都不需要實際建立物件即可呼叫。

classmethod是如何綁定到類別上的?

@classmethod
def func(cls, args...)

在上面的範例中可以看到,第一個參數雖然沒有用self來命名,但仍舊使用了cls這個名稱。而就是這個 "cls" 把classmethod綁定在類別上的。

參數cls是什麼?

其實就如同類別內的其他函數一樣,利用self來指向自己,但在classmethod中的第一個參數,指向的自己是該類別的記憶體位置。

區別在於一般函數的第一個參數指的是該物件的記憶體位置,classmethod的第一個參數指的是該類別的記憶體位置。

順道一提,classmethod的第一個參數也是隨你任意命名,不一定要叫”cls”。

classmethod與staticmethod的差別?

因為classmethod利用的cls來把自己綁定在類別上,所以classmethod可以透過cls來呼叫類別內的成員;但staticmethod只能操作傳入的參數。

#Exampleclass Person:
age = 25

def printAge(cls):
print('The age is:', cls.age)

# create printAge class method
Person.printAge = classmethod(Person.printAge)

Person.printAge()
#OutputThe age is: 25

什麼時候用classmethod比較好?

1. 工廠模式 factory pattern

#Examplefrom datetime import date# random Person
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def fromBirthYear(cls, name, birthYear):
return cls(name, date.today().year - birthYear)
def display(self):
print(self.name + "'s age is: " + str(self.age))
person = Person('Adam', 19)
person.display()
person1 = Person.fromBirthYear('John', 1985)
person1.display()
#OutputAdam's age is: 19
John's age is: 31

在這裡我們擁有兩個物件:person以及person1,分別是Adam跟John,接下來我們用他們的姓名來稱呼他們。

Adam是透過標準的建構子(Constructor)來創造的,參數是姓名跟年紀;而John是利用factory method也就是 “frombirthYear” 透過傳入姓名與出生年份來創造。

在程式碼中我們可以看到回傳cls(name, date.today.year - birthYear),這樣的寫法其實就是創造一個物件並回傳,等價於
Person(name, date.today().year - birthYear)。

透過這種方式來讓你能夠有彈性的創造物件,不局限於限定的建構子參數,也省下建立Factory的步驟。

2. 在繼承時創建正確的物件

承接上面的Factory 範例,如果用staticmethod可不可以做factory?

也可以。

#Examplefrom datetime import date

# random Person
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

@staticmethod
def fromFathersAge(name, fatherBirthYear, fatherPersonAgeDiff):
return Person(name, date.today().year - fatherBirthYear -fatherPersonAgeDiff)

@classmethod
def fromBirthYear(cls, name, birthYear):
return cls(name, date.today().year - birthYear)

def display(self):
print(self.name + "'s age is: " + str(self.age))

class Man(Person):
sex = 'Male'

man = Man.fromBirthYear('John', 1985)
print(isinstance(man, Man))

man1 = Man.fromFathersAge('John', 1965, 20)
print(isinstance(man1, Man))
#OutputTrue
False

在範例裡面我們多用了一個從爸爸出生年份與兒子年齡差距來建立物件的方法 — fromFathersAge,直接看到最後回傳的部分,是直接寫死 Person 類別,沒辦法像classmethod一樣利用cls來呼叫自身類別。

兩者的做法差別最大的地方在於:當你利用子類別(sub class)呼叫來自父類別(parent class)函數時,你創造出來的物件會被限定在父類別這個階層,而不是你呼叫的子類別。

因為你當初寫死類別了,staticmethod無法自動衍生至子類別。這點從我們的測試結果就可以看的出來。

--

--

莊子弘

文字工作者,不過周一到週五寫的都是程式碼。閒暇時間會分享一些心得,包含技術文章或影集書籍觀後感。