type 類別

May 12, 2022

Python 可以使用類別來定義物件的藍圖,每個物件實例本身都有 __class__ 屬性,參考至實例建構時使用之類別。

類別是 type 實例

類別本身也有 __class__ 屬性,那麼它會參考至什麼?

>>> class Some:
...     pass
...
>>> s = Some()
>>> s.__class__
<class '__main__.Some'>
>>> Some.__class__
<class 'type'>
>>>

類別的 __class__ 參考至 type 類別,類別也是一個物件,是 type 類別的實例。

在〈callable 物件〉談過,類別可以定義 __call__ 方法,讓類別實例成為 callable 物件,類別是 type 的實例,type 定義了 __call__ 方法,因此要建立類別實例,不需要使用 new,而是像函式呼叫那,在類別名稱後接上圓括號,你也可以直接呼叫類別的 __call__ 方法:

>>> class Some:
...     def __init__(self):
...         print('__init__')
...
>>> Some()
__init__
<__main__.Some object at 0x000002708FEF1FA0>
>>> Some.__call__()
__init__
<__main__.Some object at 0x000002708FEF1190>
>>>

type 定義了 __call__,因此每個類別都會有 __call__ 方法,其定義的行為中,會呼叫 __new____init__,因此上例中會看到顯示了 '__init__'

透過 type 建立類別

既然每個類別都是 type 類別的實例,那麼有沒有辦法撰寫程式碼,直接使用 type 類別來建構出類別呢?可以!必須先知道的是,使用 type 類別建構類別時,必須指定三個引數,分別是類別名稱(字串)、類別的父類別(tuple) 與類別的屬性(dict)。

如果如下定義類別的話:

class Some:
    x = 10
    def __init__(self, arg):
        self.arg = arg

    def doSome(self):
        self.arg += 1

Python 在剖析完類別之後,會建立 x 名稱參考至 10、建立 __init__doSome 名稱分別參考至各自定義的函式,然後呼叫 type 建立實例,並指定給 Some 名稱,也就是類似於:

>>> def __init__(self, arg):
...     self.arg = arg
...
>>> def doSome(self):
...     self.arg += 1
...
>>> x = 10
>>> Some = type('Some', (object,), {'x': 10, '__init__' : __init__, 'doSome' : doSome})
>>> s = Some(100)
>>> s.arg
100
>>>

在 Python 中,類別是 type 的實例,如果有方法能介入 type 建立實例與初始化的過程,就有辦法改變類別的行為,這就是 meta 類別的基本概念。

這與裝飾器的概念不同,要對類別使用裝飾器時,類別本身已經產生,也就是已經產生了 type 實例,然後才去裝飾類別的行為;meta 類別是直接介入 type 建構與初始化類別的過程,時機點並不相同。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter