隨手扎
實現Python裏面的classmethod和staticmethod
在Python裏面存在著兩個用於定義物件方法的內建函式(built-in function) – classmethod
和staticmethod
。其實我一開始在學的時候根本沒用過他們(´・ω・`),先不管他們常不常用,雖然應該就是常用所以才會被放進內建函式,不過因爲常見的特殊語法–修飾器,使得隨後定義的方法與實際作用變得不同,常讓初學的人摸不著頭緒。今天就來以實現說說這兩個函式。
預備知識
首先你要先有基本的Python概念。接著最好已經很瞭解 修飾器 的作用。如果不瞭解修飾器,強烈建議先閱讀函式修飾器。 雖然本文在最後也爲實現一個相似於修飾器的函式版本 – [wrap](#修飾器的作用與實現 – wrap) ,不過其又涉及到其他知識,並不建議直接閱讀。
Check List
- 我瞭解最基本Python
- 我知道Python裏面修飾器的作用
實現classmethod和staticmethod – MyClassmethod, MyStaticmethod
在實現之前,必須先瞭解classmethod和staticmethod有甚麼特性?在使用後會發生什麼事情?
一般而言,在定義物件方法時會給與def methodName(self, *args, **kargs)
,其中self
表示物件實例本身。如果你瞭解 Lua 會發現是同樣的顯式引用發法,並且Lua中的語法糖instance:method(...)
(相當於class.method(instance, ...)
),幾乎就對應於Python裏面的Instance.method(*args, **kargs)
(相當於Class.method(Instance, *args, **kargs)
)。
相較之下,C++、Java裏面的this
,或是VB裏面的Me
,就是隱式用法。我自己是比較喜歡顯式引用,比較明確。
現在我們考慮下面類別:
class C1:
a = 1
# seter a
def s1a(self, a):
self.a = a
return self.a
# geter a
def g1a(self):
return self.a
# set class a
@classmethod
def s2a(cls, a):
cls.a = a
return cls.a
# get class a
@classmethod
def g2a(cls, ):
return cls.a
# static method
@staticmethod
def s(string = "Hello, World"):
return string
# return self
def r1(self):
return self
# return class
@classmethod
def r2(cls):
return cls
可以發現staticmethod
之後的方法定義少了self
;而classmethod
的則變成的cls
,代指類別(class)本身。
現在建立一個實例(instance, I1)來驗證看看:
I1 = C1()
assert I1.g1a() == 1, "init error"
assert I1.a == 1, "init error"
assert C1.a == 1, "Class a error"
assert I1.s1a(9) == 9, "set a fail"
assert I1.s2a(9) == 9, "set class a fail"
assert I1.g1a() == 9, "a not correct, need 9 , because setted"
assert I1.g2a() == 9, "class a not correct, need 9 , because setted"
assert I1.a == 9, "a not correct, need 9 , because setted"
assert I1.__class__.a == 9, "class a not correct, need 9 , because setted"
assert C1.a == 9, "class a not correct, need 9 , because setted"
assert I1.s() == "Hello, World", "static method error"
assert C1.s() == "Hello, World", "class static method error"
assert I1.s("Hi") == "Hi", "static method error"
assert C1.s("Hi") == "Hi", "class static method error"
可以發先透過實例I1
設定類別屬性之後(s2a
),類別的屬性(C1.a
)也確確實實發生改變 => cls
代指class
。
好,現在該來實現自己的方法了。
def myClassmethod(m, *arg, **karg):
def _m(cls, *arg, **karg):
cls = cls if isinstance(cls, type(int)) else cls.__class__
return m(cls, *arg, **karg)
return _m
def myStaticmethod(m, *arg, **karg):
def _m(*arg, **karg):
if len(arg) > 0:
if m.__name__ in dir(arg[0].__class__):
return getattr(arg[0].__class__, m.__name__)(*arg[1:], **karg)
return m(*arg, **karg)
return _m
myClassmethod
將第一個參數替換為class,而myStaticmethod
依情況忽略第一個參數。
新增日期: 日 4月 21 22:02:31 CST 2019
感謝 WenLi Zhuang 指出,目前myStaticmethod的實現實際存在問題。
下面雖然通過測試了,但我後來才發現忘記忘記做上面最後兩個測試。(最後兩個就出錯了)
此外,在第一個參數給類別或實例時,也很可能發生預期外的情況,因此這樣的並不是一個好的實現。在撰寫時,嘗試了幾個staticmethod的情況。結果實現仍不如預期,雖然我有想過用而外的函式或物件去儲存靜態方法,不過最終沒有如此實現。
並且,後來才知道 良葛格 已經寫過相關的文章。推薦直接連結過去看看。
(O_O) 不是我以前看過忘了,就是我真的沒看到。再回去看一次,收穫良多。
現在來測試看看:
class C2:
a = 1
def s1a(self, a):
self.a = a
return self.a
def g1a(self):
return self.a
@myClassmethod
def s2a(cls, a):
cls.a = a
return cls.a
@myClassmethod
def g2a(cls, ):
return cls.a
@myStaticmethod
def s(string = "Hello, World"):
return string
def r1(self):
return self
@myClassmethod
def r2(cls):
return cls
I2 = C2()
assert I2.g1a() == 1, "init error"
assert I2.a == 1, "init error"
assert C2.a == 1, "Class a error"
assert I2.s1a(9) == 9, "set a fail"
assert I2.s2a(9) == 9, "set class a fail"
assert I2.g1a() == 9, "a not correct, need 9 , because setted"
assert I2.g2a() == 9, "class a not correct, need 9 , because setted"
assert I2.a == 9, "a not correct, need 9 , because setted"
assert I2.__class__.a == 9, "class a not correct, need 9 , because setted"
assert C2.a == 9, "class a not correct, need 9 , because setted"
assert I2.s() == "Hello, World", "static method error"
assert C2.s() == "Hello, World", "class static method error"
建立一個與C1幾乎一樣的類別C2。並產生實例I2去測試看看。恩,一切皆好~
完整程式碼如下,執行之後沒有出現任何錯誤,內建的方法於實現的方法都通過測試:
修飾器的作用與實現 – wrap
本節目標:
實現一個函式 wrap , 其接受兩個函式,使第一個函式的作用,更新為經過第二個參數修飾函式的結果。
首先定義一個函式A和修飾函式B
def A(end="\n"):
print("Hello", end=end)
return "Hello"
def B(f):
def _f():
r = f("")
print(", World")
return r + ", World"
return _f
接著是重頭戲,要來實現函式wrap:
def wrap(f1, f2):
G = globals()
f1_name = f1.__name__
G[f1_name] = f2(f1)
我們要把存在global的f1函式重新覆寫掉。首先我們要先知道該覆寫誰? 然後使用修飾函式產生新的 函式內容。最後在將結過覆寫回去。
接著來驗證看看:
# A()應該要先是原本的樣子
assert A() == "Hello", "define A error"
# 進行替換後,A()應該呈現替換後的結果。
wrap(A,B)
assert A() == "Hello, World", "wrap A to B(A) fail"
完整程式碼如下,你也可以在Ideone執行看看,應該不會跑出任何錯誤:
LINE Pay贊助 信用卡小額贊助