2013年12月29日 星期日

簡介 python 的物件生成 ( how to generate an instance in python)

    在開始介紹 python 怎麼產生 instnace 前,先來看一段簡單的程式碼


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)

沒有留言:

張貼留言