如何正確理解@classmethod與@staticmethod

Python面向對象編程中,類中定義的方法能夠是 @classmethod 裝飾的類方法,也能夠是 @staticmethod 裝飾的靜態方法,用的最多的仍是不帶裝飾器的實例方法,若是把這幾個方法放一塊,對初學者來講無疑是一頭霧水,那咱們該如何正確地使用它們呢?html

先來看一個簡單示例:python

class A(object):
    def m1(self, n):
        print("self:", self)

 @classmethod
    def m2(cls, n):
        print("cls:", cls)

 @staticmethod
    def m3(n):
        pass

a = A()
a.m1(1) # self: <__main__.A object at 0x000001E596E41A90>
A.m2(1) # cls: <class '__main__.A'>
A.m3(1)複製代碼

我在類中一共定義了3個方法,m1 是實例方法,第一個參數必須是 self(約定俗成的)。m2 是類方法,第一個參數必須是cls(一樣是約定俗成),m3 是靜態方法,參數根據業務需求定,無關緊要。當程序運行時,大概發生了這麼幾件事(結合下面的圖來看)。web

  • 第一步:代碼從第一行開始執行 class 命令,此時會建立一個類 A 對象(沒錯,類也是對象,一切皆對象嘛)同時初始化類裏面的屬性和方法,記住,此刻實例對象還沒建立出來。
  • 第2、三步:接着執行 a=A(),系統自動調用類的構造器,構造出實例對象 a
  • 第四步:接着調用 a.m1(1) ,m1 是實例方法,內部會自動把實例對象傳遞給 self 參數進行綁定,也就是說, self 和 a 指向的都是同一個實例對象。
  • 第五步:調用A.m2(1)時,python內部隱式地把類對象傳遞給 cls 參數,cls 和 A 都指向類對象。

嚴格意義上來講,左邊的都是變量名,是對象的引用,右邊纔是真正的對像,爲了描述方便,我直接把 a 稱爲對象,你應該明白我說對象實際上是它所引用右邊的那個真正的對象。編程

再來看看每一個方法各有什麼特性微信

實例方法

print(A.m1)
# A.m1在py2中顯示爲<unbound method A.m1>
<function A.m1 at 0x000002BF7FF9A488>

print(a.m1)
<bound method A.m1 of <__main__.A object at 0x000002BF7FFA2BE0>>複製代碼

A.m1是一個尚未綁定實例對象的方法,對於未綁定方法,調用 A.m1 時必須顯示地傳入一個實例對象進去,而 a.m1是已經綁定了實例的方法,python隱式地把對象傳遞給了self參數,因此再也不手動傳遞參數,這是調用實例方法的過程。函數

A.m1(a, 1)
# 等價 
a.m1(1)複製代碼

若是未綁定的方法 A.m1 不傳實例對象給 self 時,就會報參數缺失錯誤,在 py3 與 py2 中,二者報的錯誤不一致,python2 要求第一個參數self是實例對象,而python3中能夠是任意對象。tornado

A.m1(1)
TypeError: m1() missing 1 required positional argument: 'n'複製代碼

類方法

print(A.m2)
<bound method A.m2 of <class '__main__.A'>>

print(a.m2)
<bound method A.m2 of <class '__main__.A'>>複製代碼

m2是類方法,無論是 A.m2 仍是 a.m2,都是已經自動綁定了類對象A的方法,對於後者,由於python能夠經過實例對象a找到它所屬的類是A,找到A以後自動綁定到 cls。ui

A.m2(1) 
 # 等價
 a.m2(1)複製代碼

這使得咱們能夠在實例方法中經過使用 self.m2()這種方式來調用類方法和靜態方法。spa

def m1(self, n):
    print("self:", self)
    self.m2(n)複製代碼

靜態方法

print(A.m3)
<function A.m3 at 0x000002BF7FF9A840>

print(a.m3)
<function A.m3 at 0x000002BF7FF9A840>複製代碼

m3是類裏面的一個靜態方法,跟普通函數沒什麼區別,與類和實例都沒有所謂的綁定關係,它只不過是碰巧存在類中的一個函數而已。不管是經過類仍是實例均可以引用該方法。.net

A.m3(1) 
 # 等價
 a.m3(1)複製代碼

以上就是幾個方法的基本介紹。如今把幾個基本的概念理清楚了,那麼如今來講說幾個方法之間的使用場景以及他們之間的優缺點。

應用場景

靜態方法的使用場景:

若是在方法中不須要訪問任何實例方法和屬性,純粹地經過傳入參數並返回數據的功能性方法,那麼它就適合用靜態方法來定義,它節省了實例化對象的開銷成本,每每這種方法放在類外面的模塊層做爲一個函數存在也是沒問題的,而放在類中,僅爲這個類服務。例以下面是微信公衆號開發中驗證微信簽名的一個例子,它沒有引用任何類或者實例相關的屬性和方法。

from hashlib import sha1
import tornado.web

class SignatureHandler(tornado.web.RequestHandler):
    def get(self):
        """ 根據簽名判斷請求是否來自微信 """
        signature = self.get_query_argument("signature", None)
        echostr = self.get_query_argument("echostr", None)
        timestamp = self.get_query_argument("timestamp", None)
        nonce = self.get_query_argument("nonce", None)
        if self._check_sign(TOKEN, timestamp, nonce, signature):
            logger.info("微信簽名校驗成功")
            self.write(echostr)
        else:
            self.write("你不是微信發過來的請求")

 @staticmethod
    def _check_sign(token, timestamp, nonce, signature):
        sign = [token, timestamp, nonce]
        sign.sort()
        sign = "".join(sign)
        sign = sha1(sign).hexdigest()
        return sign == signature複製代碼

類方法的使用場景有:

做爲工廠方法建立實例對象,例如內置模塊 datetime.date 類中就有大量使用類方法做爲工廠方法,以此來建立date對象。

class date:

    def __new__(cls, year, month=None, day=None):
        self = object.__new__(cls)
        self._year = year
        self._month = month
        self._day = day
        return self

 @classmethod
    def fromtimestamp(cls, t):
        y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
        return cls(y, m, d)

 @classmethod
    def today(cls):
        t = _time.time()
        return cls.fromtimestamp(t)複製代碼

若是但願在方法裡面調用靜態類,那麼把方法定義成類方法是合適的,由於要是定義成靜態方法,那麼你就要顯示地引用類A,這對繼承來講可不是一件好事情。

class A:

    @staticmethod
    def m1()
        pass

    @staticmethod
    def m2():
        A.m1() # bad

    @classmethod
    def m3(cls):
        cls.m1() # good複製代碼

其實也不算是什麼深刻理解吧,最多算是明白怎麼用,真要深刻理解恐怕還要另寫一篇文章,有興趣的能夠去了解一下Python的描述符。

同步發佈於博客:foofish.net/different_b…

公衆號:python之禪
公衆號:python之禪
相關文章
相關標籤/搜索