超多超多的文章都在討論究竟 Python 裡頭的 staticmethod && staticmethod && instance method 差在哪邊?
可以參考 這篇, 這篇 或 這篇…(以上都是好幾千個 upvote 的 Q&A), 半夜睡不著覺 而且對於知識狂熱的份子可以去好好讀它…
底下就用 “我自己的理解” 來湊熱鬧 寫篇文章解說解說
Prerequest
- 對於 Python 物件導向要有某種程度的熟悉
- 對於
@staticmethod
&@classmethod
多多少少有種模糊的認識
範例
底下範例我打算寫個元件(類別), 你只要給它 ‘date 物件’, 他就能告訴你:
- 這天所在的那個月的第一天
- 這天所在的那個月的最後一天
- 這天所在的那週的第一天的日期
- 這天所在的那週的最後一天的日期
from datetime import timedelta, date
class DateTimeTools:
def first_day_of_month(self, dt: date) -> date:
return date(dt.year, dt.month, 1)
def last_day_of_month(self, dt: date) -> date:
dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
return date(dt2.year, dt2.month, 1) - timedelta(days=1)
def first_day_of_week(self, dt: date) -> date:
return dt - timedelta(days=dt.isoweekday() % 7)
def last_day_of_week(self, dt: date) -> date:
return self.first_day_of_week(dt=dt) + timedelta(days=6)
dd = date(2021, 9, 21)
a = DateTimeTools()
print(a.first_day_of_month(dt=dd)) # 2020-09-01
print(a.last_day_of_month(dt=dd)) # 2020-09-30
print(a.first_day_of_week(dt=dd)) # 2020-09-20
print(a.last_day_of_week(dt=dd)) # 2020-09-26
因為上面的範例使用上有點不方便(每次都要傳參數給裡頭的方法), 稍微修改一下後變成下面這樣
from datetime import timedelta, date
class DateTimeTools:
def __init__(self, dt: date):
self._dt = dt
def first_day_of_month(self) -> date:
return date(self._dt.year, self._dt.month, 1)
def last_day_of_month(self) -> date:
dt2 = date(self._dt.year + 1, 1, 1) if self._dt.month == 12 else date(self._dt.year, self._dt.month + 1, 1)
return date(dt2.year, dt2.month, 1) - timedelta(days=1)
def first_day_of_week(self) -> date:
return self._dt - timedelta(days=self._dt.isoweekday() % 7)
def last_day_of_week(self) -> date:
return self.first_day_of_week() + timedelta(days=6)
dd = date(2020, 9, 21)
a = DateTimeTools(dt=dd)
print(a.first_day_of_month()) # 2020-09-01
print(a.last_day_of_month()) # 2020-09-30
print(a.first_day_of_week()) # 2020-09-20
print(a.last_day_of_week()) # 2020-09-26
善用 實例屬性, 將來呼叫方法時, 就不用再輸入參數
然而使用上, 或許你會覺得不必這麼麻煩,
我們還是回到最一開始的那種情況, 我需要把什麼樣的日期作轉換, 當下再給他日期就好
只是每次都得實例化很不直覺, 改成更好用的方式不就好了?
from datetime import timedelta, date
class DateTimeTools:
@classmethod
def first_day_of_month(cls, dt: date) -> date:
return date(dt.year, dt.month, 1)
@classmethod
def last_day_of_month(cls, dt: date) -> date:
dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
return date(dt2.year, dt2.month, 1) - timedelta(days=1)
@classmethod
def first_day_of_week(cls, dt: date) -> date:
return dt - timedelta(days=dt.isoweekday() % 7)
@classmethod
def last_day_of_week(cls, dt: date) -> date:
return DateTimeTools.first_day_of_week(dt=dt) + timedelta(days=6)
dd = date(2020, 9, 21)
print(DateTimeTools.first_day_of_month(dt=dd)) # 2020-09-01
print(DateTimeTools.last_day_of_month(dt=dd)) # 2020-09-30
print(DateTimeTools.first_day_of_week(dt=dd)) # 2020-09-20
print(DateTimeTools.last_day_of_week(dt=dd)) # 2020-09-26
現在全透過 類別方法 來呼叫即可, 而類別名稱就只剩下把這些方法歸類到同一個地方的作用而已
此外, 也可以改成 靜態方法, 讓程式碼再少一些
from datetime import timedelta, date
class DateTimeTools:
@staticmethod
def first_day_of_month(dt: date) -> date:
return date(dt.year, dt.month, 1)
@staticmethod
def last_day_of_month(dt: date) -> date:
dt2 = date(dt.year + 1, 1, 1) if dt.month == 12 else date(dt.year, dt.month + 1, 1)
return date(dt2.year, dt2.month, 1) - timedelta(days=1)
@staticmethod
def first_day_of_week(dt: date) -> date:
return dt - timedelta(days=dt.isoweekday() % 7)
@staticmethod
def last_day_of_week(dt: date) -> date:
return DateTimeTools.first_day_of_week(dt=dt) + timedelta(days=6)
dd = date(2020, 9, 21)
print(DateTimeTools.first_day_of_month(dt=dd)) # 2020-09-01
print(DateTimeTools.last_day_of_month(dt=dd)) # 2020-09-30
print(DateTimeTools.first_day_of_week(dt=dd)) # 2020-09-20
print(DateTimeTools.last_day_of_week(dt=dd)) # 2020-09-26
在這個範例裡面, @staticmethod
& @classmethod
看起來只差一點點, 這並不意味著他們是一樣的
後記
最後不免會有疑問, 那什麼時候用 staticmethod? 又啥時用 classmethod
我的回答是(非專業見解), 其實我不是非常確定有沒有哪些情境, 其中一種方式是絕對優於另一者
目前書上, 網路上 東看西看的理解, 多半都只是在說 類別方法多了個 cls
, 至於使用情境
目前依然是一頭霧水.
因此我最終的結論是, 能正常使用就好. 符合需求就好. 習慣就好. 符合團隊要求就好.