classmethod, staticmethod, instance method in Python

Share on:

超多超多的文章都在討論究竟 Python 裡頭的 staticmethod && staticmethod && instance method 差在哪邊?

可以參考 這篇, 這篇這篇...(以上都是好幾千個 upvote 的 Q&A), 半夜睡不著覺 而且對於知識狂熱的份子可以去好好讀它...

底下就用 "我自己的理解" 來湊熱鬧 寫篇文章解說解說

Prerequest

  • 對於 Python 物件導向要有某種程度的熟悉
  • 對於 @staticmethod & @classmethod 多多少少有種模糊的認識

範例

底下範例我打算寫個元件(類別), 你只要給它 'date 物件', 他就能告訴你:

  • 這天所在的那個月的第一天
  • 這天所在的那個月的最後一天
  • 這天所在的那週的第一天的日期
  • 這天所在的那週的最後一天的日期
 1from datetime import timedelta, date
 2
 3class DateTimeTools:
 4    def first_day_of_month(self, dt: date) -> date:
 5        return date(dt.year, dt.month, 1)
 6    
 7    def last_day_of_month(self, dt: date) -> date:
 8        dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
 9        return date(dt2.year, dt2.month, 1) - timedelta(days=1)
10    
11    def first_day_of_week(self, dt: date) -> date:
12        return dt - timedelta(days=dt.isoweekday() % 7)
13    
14    def last_day_of_week(self, dt: date) -> date:
15        return self.first_day_of_week(dt=dt) + timedelta(days=6)
16
17dd = date(2021, 9, 21)
18
19a = DateTimeTools()
20print(a.first_day_of_month(dt=dd))  # 2020-09-01
21print(a.last_day_of_month(dt=dd))   # 2020-09-30
22print(a.first_day_of_week(dt=dd))   # 2020-09-20
23print(a.last_day_of_week(dt=dd))    # 2020-09-26

因為上面的範例使用上有點不方便(每次都要傳參數給裡頭的方法), 稍微修改一下後變成下面這樣

 1from datetime import timedelta, date
 2
 3class DateTimeTools:
 4    def __init__(self, dt: date):
 5        self._dt = dt
 6
 7    def first_day_of_month(self) -> date:
 8        return date(self._dt.year, self._dt.month, 1)
 9    
10    def last_day_of_month(self) -> date:
11        dt2 = date(self._dt.year + 1, 1, 1) if self._dt.month == 12 else date(self._dt.year, self._dt.month + 1, 1)
12        return date(dt2.year, dt2.month, 1) - timedelta(days=1)
13    
14    def first_day_of_week(self) -> date:
15        return self._dt - timedelta(days=self._dt.isoweekday() % 7)
16    
17    def last_day_of_week(self) -> date:
18        return self.first_day_of_week() + timedelta(days=6)
19
20dd = date(2020, 9, 21)
21
22a = DateTimeTools(dt=dd)
23print(a.first_day_of_month())  # 2020-09-01
24print(a.last_day_of_month())   # 2020-09-30
25print(a.first_day_of_week())   # 2020-09-20
26print(a.last_day_of_week())    # 2020-09-26

善用 實例屬性, 將來呼叫方法時, 就不用再輸入參數

然而使用上, 或許你會覺得不必這麼麻煩,

我們還是回到最一開始的那種情況, 我需要把什麼樣的日期作轉換, 當下再給他日期就好

只是每次都得實例化很不直覺, 改成更好用的方式不就好了?

 1from datetime import timedelta, date
 2
 3
 4class DateTimeTools:
 5
 6    @classmethod
 7    def first_day_of_month(cls, dt: date) -> date:
 8        return date(dt.year, dt.month, 1)
 9    
10    @classmethod
11    def last_day_of_month(cls, dt: date) -> date:
12        dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
13        return date(dt2.year, dt2.month, 1) - timedelta(days=1)
14    
15    @classmethod
16    def first_day_of_week(cls, dt: date) -> date:
17        return dt - timedelta(days=dt.isoweekday() % 7)
18    
19    @classmethod
20    def last_day_of_week(cls, dt: date) -> date:
21        return DateTimeTools.first_day_of_week(dt=dt) + timedelta(days=6)
22
23
24dd = date(2020, 9, 21)
25
26print(DateTimeTools.first_day_of_month(dt=dd))  # 2020-09-01
27print(DateTimeTools.last_day_of_month(dt=dd))   # 2020-09-30
28print(DateTimeTools.first_day_of_week(dt=dd))   # 2020-09-20
29print(DateTimeTools.last_day_of_week(dt=dd))    # 2020-09-26

現在全透過 類別方法 來呼叫即可, 而類別名稱就只剩下把這些方法歸類到同一個地方的作用而已

此外, 也可以改成 靜態方法, 讓程式碼再少一些

 1from datetime import timedelta, date
 2
 3
 4class DateTimeTools:
 5
 6    @staticmethod
 7    def first_day_of_month(dt: date) -> date:
 8        return date(dt.year, dt.month, 1)
 9    
10    @staticmethod
11    def last_day_of_month(dt: date) -> date:
12        dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
13        return date(dt2.year, dt2.month, 1) - timedelta(days=1)
14    
15    @staticmethod
16    def first_day_of_week(dt: date) -> date:
17        return dt - timedelta(days=dt.isoweekday() % 7)
18    
19    @staticmethod
20    def last_day_of_week(dt: date) -> date:
21        return DateTimeTools.first_day_of_week(dt=dt) + timedelta(days=6)
22
23
24dd = date(2020, 9, 21)
25
26print(DateTimeTools.first_day_of_month(dt=dd))  # 2020-09-01
27print(DateTimeTools.last_day_of_month(dt=dd))   # 2020-09-30
28print(DateTimeTools.first_day_of_week(dt=dd))   # 2020-09-20
29print(DateTimeTools.last_day_of_week(dt=dd))    # 2020-09-26

在這個範例裡面, @staticmethod & @classmethod 看起來只差一點點, 這並不意味著他們是一樣的

後記

最後不免會有疑問, 那什麼時候用 staticmethod? 又啥時用 classmethod

我的回答是(非專業見解), 其實我不是非常確定有沒有哪些情境, 其中一種方式是絕對優於另一者

目前書上, 網路上 東看西看的理解, 多半都只是在說 類別方法多了個 cls, 至於使用情境

目前依然是一頭霧水.

因此我最終的結論是, 能正常使用就好. 符合需求就好. 習慣就好. 符合團隊要求就好.

comments powered by Disqus