1. 程式人生 > >Python 的例項方法、類方法和靜態方法

Python 的例項方法、類方法和靜態方法

Python 的例項方法、類方法和靜態方法

轉載請註明出處:https://blog.csdn.net/jpch89/article/details/84442713


文章目錄


0. 參考資料


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 引數,該例項的類未必是定義類方法的類。如下例:

# 父類
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 不會傳遞 selfcls,以此來限制靜態方法的許可權。所以靜態方法不能獲取例項或者類的狀態。
它們就像普通函式一樣,只不過隸屬於類和該類的每個例項的名稱空間。


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. 使用類方法實現披薩工廠

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 類。

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
  • 靜態方法可以用例項或者類呼叫。
    靜態方法無法獲取到 clsself
    靜態方法上面要加 @staticmethod 裝飾器。

  • 類方法和靜態方法,從某種程度上傳達了類的設計意圖,使程式碼易於維護。


完成於 2018.11.24