iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Software Development

Python十翼:與未來的自己對話系列 第 15

[Day15] 五翼 - Metaclasses:Class Creation

  • 分享至 

  • xImage
  •  

今天我們分享class是如何生成的,其實關鍵都在type這個built-in

type

type有兩種常用的使用情況:

  1. 接受一個參數時,會回傳該參數的type
  2. 接受三個參數時,會回傳一個class,是一種動態生成class的方式。

typesignature如下:

type(cls_name, cls_bases, cls_dict)
  • cls_name為想建立class的名字。
  • cls_bases為新建立classMRO上所有class,為一tuple。如果給予一個空的tuple,Python會自動將object加進去,即(object, )
  • cls_dict含有想建立class的所有資訊。

利用type建立class

# 01
class MyClass:
    def __init__(self, x):
        self.x = x

# 01是一個基本的class,我們可以將其拆為幾個部份,分別對應利用type建立class的過程:

  • class是能夠建立class這種型態的關鍵字,在這裡我們想用type取代。
  • cls_name'MyClass'
  • MyClass為繼承object而來,即cls_bases(object, )或以空tuple代替。
  • MyClass內含有functionattribute的部份為cls_body。其實是一堆字串,我們需要一個方法來轉化這些字串,將轉換後的狀態記錄於cls_dict,使其能為type所用,而內建的exec正好可以派上用場。

# 02模擬利用type建立class

# 02
cls_dict = {}
cls_body = '''
def __init__(self, x):
    self.x = x
'''
exec(cls_body, globals(), cls_dict)  # populating cls_body into cls_dict
cls_name = 'MyClass'
cls_bases = ()
MyClass = type(cls_name, cls_bases, cls_dict)

利用MyType建立class

既然type是一種class,表示我們可以繼承type建立一個新的MyType。這麼一來,我們就可以overwrite type.__new__,在建立新class的前後動點手腳,最後再用類似前面type建立class的語法,使用MyType來建立class

# 03
class MyType(type):
    def __new__(mcls, cls_name, _cls_bases, cls_dict):
        cls = super().__new__(mcls, cls_name, _cls_bases, cls_dict)
        return cls
    
... # 中間過程如# 02
MyClass = MyType(cls_name, cls_bases, cls_dict)
print(type(MyClass))  # <class '__main__.MyType'>

利用class關鍵字搭配MyType作為metaclass

可以看出使用class關鍵字來建立class,比使用MyType來的簡潔方便。但使用MyType的好處,是可以在建立class時動點手腳。那麼有沒有一種方法是既可以使用class 關鍵字來建立class,又同時具備我們在MyType中所做的操作呢?其實有的,而且我們一直隱性在用。

# 04中的MyClass1MyClass2MyClass3是等義的。class是繼承object,並由type所生成。

# 04
class MyClass1:
    def __init__(self, x):
        self.x = x


class MyClass2(object):
    def __init__(self, x):
        self.x = x


class MyClass3(object, metaclass=type):
    def __init__(self, x):
        self.x = x

所以我們只要將metaclass換為MyType,就可以繼續使用方便的class 關鍵字來建立class並同時具備於MyType中的操作。

# 04
...

class MyType(type):
    def __new__(mcls, cls_name, cls_bases, cls_dict):
        cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
        return cls


class MyClass(object, metaclass=MyType):
    def __init__(self, x):
        self.x = x

__prepare__

本日內容至此,不知道您有沒有疑惑過下面這個問題。

當我們利用MyType來建立class,其cls_dict是我們給予的。但是當利用class關鍵字搭配MyType作為metaclass這種建立class的方式時,其中的cls_dict是哪裡來的呢?

其實此cls_dict會由__prepare__提供。如果help(type),可以看出__prepare__class method

 |  Class methods defined here:
 |
 |  __prepare__(...)
 |      __prepare__() -> dict
 |      used to create the namespace for the class statement

一般來說,__prepare__會返回dict(但事實上可以返回一個mapping),接著傳遞給__new__作為其cls_dict參數(註1)。然後我們可於__new__中對cls_dict進行一些操作。最後當我們使用cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)時,會將cls_dict中的東西複製到一個新的dict中(註2)。

# 05
class MyType(type):
    @classmethod
    def __prepare__(cls, cls_name, cls_bases, **kwargs):
        print('MyType __prepare__ called')
        cls_dict = {}
        print(f'{id(cls_dict)=}')
        return cls_dict

    def __new__(mcls, cls_name, cls_bases, cls_dict):
        print('MyType __new__ called')
        print(f'{id(cls_dict)=}')
        cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
        return cls


class MyClass(metaclass=MyType):
    pass
MyType __prepare__ called
id(cls_dict)=2079093125440
MyType __new__ called
id(cls_dict)=2079093125440

# 05中我們可以看出,__prepare__先於__new__被呼叫,且可以確認__prepare__中返回的cls_dict__new__中的cls_dict是同一個obj

Python3.7之前,一個有趣的運用是回傳一個collections.OrderedDict,來保存key-value的插入順序。但是新版本的dict已經是有序了,所以除非要維護舊版本的code__prepare__漸漸失去了它的應用價值。有興趣深研的朋友可以參考Python參考文件PEP 3115

備註

註1:於__prepare__傳到__new__的過程中,Python會幫我們加上一些attribute,如__qualname__等在此dict中。

註2:cls_dict並非新生成cls__dict__。事實上cls.__dict__也僅為一mappingproxy

Code

本日程式碼傳送門


上一篇
[Day14] 五翼 - Metaclasses:Instance Creation
下一篇
[Day16] 五翼 - Metaclasses:__call__
系列文
Python十翼:與未來的自己對話30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言