天天看點

針對Python 執行個體方法、類方法和靜态方法的詳解

1. 概覽

先定義一個最簡單的 Python 3 的類:

class MyClass:
    def method(self):
        print('我是執行個體方法', self)

    @classmethod
    def classmethod(cls):
        print('我是類方法', cls)

    @staticmethod
    def staticmethod():
        print('我是靜态方法')
           

1.1 執行個體方法

第一個方法 method(self) 方法是 執行個體方法 instance method 。 當 method 被調用時, self 參數指向 MyClass 類的一個執行個體。 執行個體方法可以通過 self 自由地通路同一對象的屬性和其它方法,這樣它們可以修改執行個體的狀态。 注意執行個體方法可以通過

self.__class__

屬性來擷取到類,是以執行個體方法也可以更改類的狀态。

1.2 類方法

第二個方法 classmethod(cls) 是 類方法 class method 。 上面需要寫一個 @classmethod 裝飾器。 類方法接收一個 cls 參數,當該方法被調用的時候,它指向類(而不是類的執行個體)。 類方法隻有 cls 參數,是以它 不能 修改執行個體的狀态。 修改執行個體的狀态必須要有 self 參數。 類方法隻能修改類的狀态,類狀态的更改會作用于所有該類的執行個體。

1.3 靜态方法

第三個方法 staticmethod() 是 靜态方法 static method 。 它上面要有一個 @staticmethod 裝飾器。 靜态方法不能修改類或者執行個體的狀态,它受限于它所接收的參數。 我們一般用這種方法來隔離命名空間。

2. 實際應用

2.1 調用執行個體方法

首先建立一個執行個體,然後調用一下執行個體方法:

obj = MyClass()

# 調用執行個體方法
obj.method()
"""
我是執行個體方法 <__main__.MyClass object at 0x00000213E209B898>
"""
           

還可以這樣調用:

MyClass.method(obj)
"""
我是執行個體方法 <__main__.MyClass object at 0x00000213E209B898>
"""
           

使用 對象.執行個體方法() 這種點号調用的形式是一個文法糖, Python 會自動把 對象 作為第一個實參,傳遞給 執行個體方法 中的 self 形參。 如果使用 類.執行個體方法(對象) 這種形式,則必須手動傳遞 對象 給 執行個體方法 的第一個參數 self 。

如果不建立執行個體就調用執行個體方法,或者是不傳入 對象 ,那麼就會出錯:

# 不建立執行個體就調用執行個體方法會發生什麼?
# 會提示缺少位置參數 self
# 執行個體方法依賴于執行個體而存在
MyClass.method()
"""
Traceback (most recent call last):
  File "test.py", line 28, in <module>
    MyClass.method()
TypeError: method() missing 1 required positional argument: 'self'
"""
           

執行個體方法可以通過

self.__class__

通路到類。

# 列印類名
print(obj.__class__.__name__)
"""
MyClass
"""
           

2.2 調用類方法

下面來調用一下類方法。

# 通過類名調用類方法
MyClass.classmethod()
# 也會自動傳遞類名作為第一個參數
"""
我是類方法 <class '__main__.MyClass'>
"""
           

通過 類.類方法() 的形式調用類方法, Python 會自動把 類 作為第一個參數傳遞給 類方法 的第一個參數 cls ,我們不用手動傳遞。

也可以用執行個體調用類方法:

# 當然也可以通過執行個體調用類方法
obj.classmethod()
"""
我是類方法 <class '__main__.MyClass'>
"""
           

通過執行個體調用類方法, Python 會把該執行個體的類傳遞給 類方法 的 cls 參數,該執行個體的類未必是定義類方法的類。如下例:

'''
學習中遇到問題沒人解答?小編建立了一個Python學習交流QQ群:531509025
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
# 父類
class Animal:
    @classmethod
    def classmethod(cls):
        print('cls是:' + str(cls.__name__))


# 子類
class Dog(Animal):
    pass


dog = Dog()
dog.classmethod()
"""
cls是:Dog
"""
# 注意不是類方法的定義類:Animal
# 而是執行個體的所屬類:Dog
           

2.3 調用靜态方法

最後調用一下靜态方法:

# 調用靜态方法
obj.staticmethod()
"""
我是靜态方法
"""
# 調用靜态方法的時候
# 點号文法不會自動傳遞任何參數
           

通過 執行個體.靜态方法() 調用靜态方法的時候, Python 不會傳遞 self 和 cls ,以此來限制靜态方法的權限。是以靜态方法不能擷取執行個體或者類的狀态。 它們就像普通函數一樣,隻不過隸屬于類和該類的每個執行個體的命名空間。

2.4 不建立執行個體調用方法

不建立執行個體,調用執行個體方法、類方法和靜态方法。

# 不建立執行個體,調用類方法
MyClass.classmethod()
"""
我是類方法 <class '__main__.MyClass'>
"""

# 不建立執行個體,調用靜态方法
MyClass.staticmethod()
"""
我是靜态方法
"""

# 不建立執行個體,調用執行個體方法
MyClass.method()
"""
Traceback (most recent call last):
  File "test.py", line 85, in <module>
    MyClass.method()
TypeError: method() missing 1 required positional argument: 'self'
"""
           

不建立執行個體,調用執行個體方法出錯。 這是可以了解的,因為我們直接通過類這個藍圖 blueprint 本身來調用執行個體方法, Python 無法給 self 傳參。

3. 使用類方法實作披薩工廠

'''
學習中遇到問題沒人解答?小編建立了一個Python學習交流QQ群:531509025
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
class Pizza:
    def __init__(self, ingredients):
        self.ingredients = ingredients

    def __repr__(self):
        return f'披薩({self.ingredients})'

    @classmethod
    def margherita(cls):
        return cls(['馬蘇裡拉奶酪', '番茄'])

    @classmethod
    def prosciutto(cls):
        return cls(['馬蘇裡拉奶酪', '番茄', '火腿'])
           

使用類方法作為工廠函數,生産不同種類的披薩。

【注】 工廠函數 factory function 工廠函數是一個函數,它根據不同的輸入,建立并傳回不同的對象。

注意在工廠函數中,沒有直接使用 Pizza 這個類名,而是使用了 cls 這個參數。 這樣的好處在于易于維護。 萬一以後要把 Pizza 這個類名改成 披薩 ,隻改動一處就行,因為類方法中用的是 cls 而不是直接寫 類名 。 這是遵循 DRY 原則的一個小技巧( Don’t repeat yourself )

現在使用工廠函數來生成幾個披薩吧:

pizza1 = Pizza.margherita()
print(pizza1)
"""
披薩(['馬蘇裡拉奶酪', '番茄'])
"""

pizza2 = Pizza.prosciutto()
print(pizza2)
"""
披薩(['馬蘇裡拉奶酪', '番茄', '火腿'])
"""
           

我們可以使用工廠函數來建立事先配置好的 Pizza 對象。 這些工廠函數内部都使用了 init 構造函數,它們提供了一個捷徑,不用記憶各種披薩配方。 從另外一個角度來看,這些 類方法可以為一個類定義多個構造函數 。

4. 何時使用靜态方法

改寫上面寫的 Pizza 類。

'''
學習中遇到問題沒人解答?小編建立了一個Python學習交流QQ群:531509025
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
import math


class Pizza:
    def __init__(self, radius, ingredients):
        self.radius = radius
        self.ingredients = ingredients

    def __repr__(self):
        return (f'披薩({self.radius!r}),'
                f'{self.ingredients!r})')

    def area(self):
        return self.circle_area(self.radius)

    @staticmethod
    def circle_area(r):
        return r ** 2 * math.pi
           

試一試使用靜态方法:

# 生成披薩
p = Pizza(4, ['馬蘇裡拉奶酪', '番茄'])
print(p)
"""
披薩(4,['馬蘇裡拉奶酪', '番茄'])
"""

# 計算披薩的面積
p.area()
print(p.area())
"""
50.26548245743669
"""

# 通過類調用靜态方法
print(Pizza.circle_area(4))
"""
50.26548245743669
"""

# 通過對象調用靜态方法
print(p.circle_area(4))
"""
50.26548245743669
"""
           

把一個方法寫成靜态方法的好處:

  • 表明它不會更改類或者執行個體的狀态
  • 更容易寫測試代碼,不用進行執行個體化就可以測試靜态方法

5. 總結

調用執行個體方法,需要一個執行個體。執行個體方法可以通過 self 來擷取執行個體。

self
self
           

類方法可以用執行個體或者類來調用。類方法可以通過 cls 擷取類本身。類方法上面要加 @classmethod 裝飾器。

  • 通過執行個體調用類方法,不用手動傳類到 cls 。 通過執行個體調用的類方法, Python 自動傳遞到 cls 的類是該對象的所屬類,不一定是定義該類方法的類。(比如父類定義了類方法,子類繼承父類。通過子類的執行個體調用父類的類方法,傳到 cls 中的參數是子類,而不是定義類方法的父類。)
  • 通過類調用類方法,也不用手動傳類到 cls 。
上一篇: mongo api
下一篇: qbxtday2筆記