python @classmethod和@staticmethod的區別

也許一些例子會有幫助:注意fooclass_foo 和static_foo參數的區別:java

class A(object):
    a = 'a'
    def __init__(self):
        self.b = 'b'

    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)

    @classmethod
    def class_foo(cls,x):
        print cls.static_foo('2')
        cls.name = 'A1'
        print dir(cls)
        print "executing class_foo(%s,%s)"%(cls,x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x

a=A()

下面是一個對象實體調用方法的經常使用方式.對象實體a被隱藏的傳遞給了第一個參數.python

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

classmethod裝飾,隱藏的傳遞給第一個參數的是對象實體的類(class A)而不是self.c++

a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

你也能夠用類調用class_foo.實際上,若是你把一些方法定義成classmethod,那麼實際上你是但願用類來調用這個方法,而不是用這個類的實例來調用這個方法.A.foo(1)將會返回一個TypeError錯誤, A.class_foo(1)將會正常運行:函數

A.foo(1)
#TypeError: unbound method foo() must be called with A instance as first argument (got str instance instead)

# 這時可想到把示例化的a傳進去怎麼樣?結果證明可行,和a.foo(1)效果同樣,但不建議這樣使用
# 其實f1 = A.foo, a = A(), f2 = a.foo, f1是unbound method,f2是bound method
# 具體可見https://my.oschina.net/u/914655/blog/1546281
A.foo(a, 1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

staticmethod來裝飾,無論傳遞給第一個參數的是self(對象實體)仍是cls(類).它們的表現都同樣:.net

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

靜態方法被用來組織類之間有邏輯關係的函數.code

foo只是個函數,可是當你調用a.foo的時候你獲得的不單單是一個函數,你獲得的是一個第一個參數綁定到a的"增強版"函數. python解析器會自動把a示例綁定到foo的第一個參數參數上對象

a綁定了foo.下面能夠知道什麼叫"綁定"了:blog

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

若是使用a.class_foo, 是A綁定到了class_foo而不是實例a.繼承

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

最後剩下靜態方法,說到底它就是一個函數. a.static_foo只是返回一個不帶參數綁定的函數. static_fooa.static_foo只須要一個參數.字符串

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

總結:

  • 普通類方法:第一個參數必須傳,做爲接收類實例對象的參數。也即持有類實例對象,須要實例化的類才能調用或者直接把實例化參數傳入第一個,見上面
  • @classmethod(類方法): 第一個參數必須傳,做爲接收類對象的參數。也即持有類對象,在不需實例化的狀況下調用類的屬性等
  • @staticmethod(靜態方法): 

區別本質有了,具體幾個應用以下:

最直接應用就是,加了這2個裝飾器,就能夠不須要實例化,直接經過 類名.函數名()來調用,有利於組織代碼,把屬於某個類的函數,但又不需實例化的給放進類裏去,同時有利於命名空間的整潔。

1. 構造多個構造函數

因爲python裏沒有java的重載機制(java裏同方法名不一樣參數認爲是不一樣的方法,python裏只要方法名惟一),可經過classmethod的特性構造多個構造函數

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

好比Date類,咱們要實例化它,必須傳入day, month, year
加入咱們有個string_date = '11-4-2017'
必須按以下方式處理

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

c++ java等語言有重載的特性,但python沒有,這時咱們就能夠經過classmethod來實現

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

# 如今直接經過調用from_string便可
date2 = Date.from_string('11-09-2012')

# 並且有如下好處
# 1. 針對此類的日期解析函數在本類裏,方便管理,並且可重用
# 2. cls是一個持有類自己的對象,而不是類的一個實例。這很酷,由於若是咱們繼承咱們的Date類,全部的孩子也將有from_string定義。

接着上面,假如字符串格式不是'dd-mm-yyyy'格式,執行from_string就不會獲得想要的結果,這時咱們要檢驗這個值,此時staticmethod就會很合適

@staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

    # usage:
    is_date = Date.is_date_valid('11-09-2012')
相關文章
相關標籤/搜索