替換一個實例方法,沒你想的那么簡單
思路一:簡單地替換
當你想對類實例的方法進行替換時,你可能想到的是直接對他進行粗暴地替換:
- class People:
- def speak(self):
- print("hello, world")
- def speak(self):
- print("hello, python")
- p = People()
- p.speak = speak
- p.speak()
但當你試著執行這段代碼的時候,就會發現行不通,它提示我們要傳入 self 參數:
- Traceback (most recent call last):
- File "/Users/MING/Code/Python/demo.py", line 12, in <module>
- p.speak()
- TypeError: speak() missing 1 required positional argument: 'self'
不對啊~ self 不是實例本身嗎?函數不是一直就這么寫的?
實際上你這么替換,speak 就變成了一個 function,而不是一個和實例綁定的 method ,你可以把替換前后的 speak 打印出來
- p = People()
- print(p.speak)
- p.speak = speak
- print(p.speak)
輸出結果如下,區別非常明顯
- <bound method People.speak of <__main__.People object at 0x10cfa7fd0>>
- <function speak at 0x10ca10040>
這種方法,只能用在替換不與實例綁定的靜態方法上,不然你每次調用的時候,就得手動傳入實例本身,但這樣調用就會變得非常怪異。
思路二:利用 im_func
有 Python 2 使用經驗的朋友,可以會知道類實例的方法,都有 im_func 和 im_class 屬性,分別指向了該方法的函數和類。
很抱歉的是,這些在 Python3 中全都取消了,意味你無法再使用 im_func 和 im_class 。
但即使你身處 Python 2 的環境下,你想通過 im_func 去直接替換函數,也仍然是有問題的。
因為在 Python2 中不推薦普通用戶對類實例的方法進行替換,所以 Python 給類實例的方法賦予了只讀屬性
思路三:非常危險的字節碼替換
表層不行,但這個方法在字節碼層面卻是可行的
這種方法,非常的粗暴且危險,他會直接影響到使用 People 的所有實例的 speak 方法,因此這種方法千萬不要使用。
思路四:利用 types 綁定方法
在 types 中有一個 MethodType,可以將普通方法與實例進行綁定。
綁定后,就可以直接替換掉原實例的 speak 方法了,完整代碼如下:
- import types
- class People:
- def speak(self):
- print("hello, world")
- def speak(self):
- print("hello, python")
- p = People()
- p.speak = types.MethodType(speak, p)
- p.speak()
這種方法,最為安全,不會影響其他實例。并且 Python 2 和 Python 3 都適用,是官方推薦的一種做法。
總結一下
- 直接替換:只適用于靜態方法
- 使用 im_func 替換:行不通
- 使用 im_func.func_code 替換字節碼:非常危險,請不要使用
- 使用 types.MethodType 進行方法綁定:安全且有效,推薦使用