靜態方法、類別方法


如果你定義了一個類別:
class Some:
def __init__(self, x):
self.x = x

def service(self, y):
print('do service...', self.x + y)

你可以透過Some實例來操作service()方法。例如:
s = Some(10)
s.service(2)  # do service... 12

表面上,上例像是:
s = Some(10)
Some.service(s, 2)

s所參考的實例,會綁定至service()的第一個參數,而所給定的引數,會指定service()方法的第二個參數,實際上在這類的情況下,service()方法是一個綁定方法(Bound method),而每個實例會有自己的綁定方法。例如:
s1 = Some(10)
service = s1.service
service(5)    # do service... 15

s2 = Some(20)
service = s2.service
service(5)    # do service... 25

而Some類別本身亦擁有一個service()函式,例如:
s1 = Some(10)
Some.service(s1, 5)  # do service... 15

s2 = Some(20)
Some.service(s2, 5)  # do service... 25

service = Some.service
service(s1, 5)       # do service... 15
service(s2, 5)       # do service... 25

如果在定義類別時,類別中的函式沒有任何參數,則該函式無法成為綁定方法,因為試圖將實例作為第一個參數時會發生錯誤。例如:
class Other:
def service():
print('do service...')

o = Other()
o.service() # TypeError: service() takes no arguments (1 given)

像上例中service()只能作為Other上的一個函式來使用:
Other.service()     # do service...

如果你在定義類別時希望某個函式,完全不要作為實例的綁定方法,也就是不要將第一個參數綁定為所建立的實例,則可以使用@staticmethod加以修飾。例如:
class Some:
@staticmethod
def service(x, y):
print('do service...', x + y)

Some.service(10, 20) # do service... 25
s = Some()
s.service(10, 20) # do service... 25
s.service(10) # TypeError: service() takes exactly 2 positional arguments (1 given)

雖然你可以透過實例來呼叫@staticmethod所修飾的靜態方法,但建議透過類別名稱來呼叫。類似的,建議透過實例來呼叫實例的綁定方法。一個沒有使用@staticmethod宣告而又帶有參數的函式,就如先前所看到的,可以用實例方法來使用,也可以用靜態方法的方式呼叫,這是Python3給的方便性,讓你不一定得使用@staticmethod來區別,只要你知道自己在作什麼。事實上,你還可以這麼作:
class Some:
def __init__(self, x):
self.x = x

def service(self, y):
print('do service...', self.x + y)

class Other:
pass

o = Other()
o.x = 100
Some.service(o, 200) # do service... 25

Some.service()的第一個參數可以任何物件,只要它有個x屬性。

你還可以使用@classmethod來修飾一個函式成為類別方法,這樣的方法第一個參數永遠綁定為類別物件本身,無論是以實例方法來呼叫,或是以靜態方法來呼叫。例如:
class Some:
def __init__(self, x):
self.x = x

@classmethod
def service(clz, y):
print('do service...', clz, y)

s = Some(10)
s.service(20) # do service... <class '__main__.Some'> 20
Some.service(30) # do service... <class '__main__.Some'> 30