class MyStr1(str): def __init__(self, a='', b=''): super(MyStr1, self).__init__() self.a = a self.b = b
這段程式碼看起來沒什麼,就繼承自 str 。然後 override __init__ 方法。所以我們可以用下面程式碼產生 instance。
a = MyStr1("1")
可是當我們執行下面程式碼時,
a = MyStr1("1", "2")
卻會出現
Traceback (most recent call last): File "", line 1, in TypeError: str() takes at most 1 argument (2 given)
這到底發生什麼事? 會出現這個錯誤,是因為 str.__new__ 這個方法只接受一個參數,而你給他兩個。
做個簡單的實驗。執行這兩行 str.__new__(str) 和 str.__new__(str, '1') 程式碼不會出錯。然而 str.__new__(str, '1', '2') 會出現上面的錯誤。所以可以合理懷疑問題真的出在這了。
接下來來談一個 class 怎麼產生 instance。當 class 要產生 instance 時,他會呼叫 class 的 metaclss 的 __call__ 方法。而 metaclass 的 __call__ 方法大概長這樣:
def __call__(cls, *args, **kwargs): obj = cls.__new__(cls, *args, **kwargs) obj.__init__(*args, **kwargs) return obj
所以執行 a = MyStr1("1", "2") 時,直譯器會先做 obj = str.__new__(str, *args, **kwargs) ,其中 *args 是 (1, 2, 3),可是因為 str.__new__ 只接受一個參數,因此發生錯誤。
要怎麼知道直譯器在建立 instance 時,會把類別名稱後面的參數全都丟進 __new__ 和 __init__ 裡呢? 用一段簡單的程式即可說明。
class ClsA(object): def __init__(self, *args, **kwargs): print 'init: ', args, kwargs def __new__(cls, *args, **kwargs): print 'new: ', args, kwargs return super(ClsA, cls).__new__(cls, *args, **kwargs) a = ClsA('a', 'b', 2)
上面程式碼,當要建立 ClsA 的程式碼,他會先執行 __new__,而餵給 __new__ 的參數則是 ClsA 後面的參數。之後再把 ClsA 後面的參數餵給 __init__。而這也說明了為什麼一開始的程式碼會有問題。因為建立 instance 時,預設行為是把類別後面的參數全都丟給 __new__ 和 __init__。可是 str.__new__最多只接受一個參數,因此出錯。
要如何解決第一個程式所遇到的問題呢?
解法1.
不要使用預設的 metaclass(type),自訂一個 meta class
class CustomMetaCls(type): def __call__(cls, *args, **kwargs): if len(args): content = args[0] else: content = '' obj = cls.__new__(cls, content) obj.__init__(*args, **kwargs) return obj class MyStr(str): __metaclass__ = CustomMetaCls def __init__(self, a='', b=''): super(MyStr, self).__init__(a) self.a = a self.b = b b = MyStr('a', 'b')
解法2.
override __new__
class MyStr1(str): def __init__(self, a='', b=''): super(MyStr1, self).__init__() self.a = a self.b = b def __new__(cls, *args, **kwargs): print args, kwargs if len(args): content = args[0] else: content = '' return str.__new__(cls, content)
沒有留言:
張貼留言