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 。