Pin-Jiun / Python

Python Document
0 stars 0 forks source link

38-staticmethod and classmethod #39

Open Pin-Jiun opened 1 year ago

Pin-Jiun commented 1 year ago

staticmethod

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

@staticmethod
def func(args, ...)

什麼時候用static method 比較好?

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

classmethod

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

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

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

參數cls是什麼?

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

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

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

classmethod與staticmethod的差別?

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

什麼時候用classmethod比較好?

  1. 工廠模式 factory pattern
#Example
from 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()
#Output
Adam'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的步驟。

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

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

也可以。

#Example
from 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))
#Output
True
False

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

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

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

https://ji3g4zo6qi6.medium.com/python-tips-5d36df9f6ad5