python的metaclass

python中的metaclass可謂熟悉而又陌生,自己開發時很少用,閱讀源碼時卻經常遇到,那麼到底什麼是metaclass呢?何時使用metaclass呢?

動態創建class的方法

假設我們需要動態創建一個class,那麼一般我們有這樣幾種方法

  1. 通過一個函數動態創建class
  2. 通過type動態創建class

1.函數動態創建class

def create_class_by_name(name):
    if name == 'dog': 
        class Dog(object):
            pass
        return Dog
    else:
        class Cat(object):
            pass
        return Cat

dy_class = create_class_by_name('hi')
print dy_class    # output: <class '__main__.Cat'>
print dy_class()  # output: <__main__.Cat object at 0x03601D10>

2.type動態創建class

type 除了可以獲取到一個對象的類型,還有另外一個功能:動態創建 class。
它的函數簽名是這樣的:_type(name, bases, dict) -> a new type_
其中:

  • name: 類名
  • bases: 父類名的tuple,用於繼承,可以爲空
  • dict: 字典,包含class attributes的 name 和 value
class ClassParent(object):
    first_name = 'John'

# 創建一個 繼承自 ClassParent 的 ClassChild,併爲 ClassChild 添加一個 age = 15 的 attribute
child_class = type('ClassChild', (ClassParent,), {'age': 15})

# child_class 形如:
class ClassChild(ClassParent):
    age = 15

child_obj = child_class()
print child_obj.first_name, child_obj.age
# output: John 15

事實上,type 關鍵字,是 python 用來創建 class 的 metaclass。可以通過 __class__ 來查看一個 class 的 metaclass:

print child_class.__class__
# output <type 'type'>

使用metaclass創建class

metaclass,即是(class of class) class 的 class,用來描述如何創建一個 class 的代碼段。

python2

在 class 的定義中,可以通過 __metaclass__ 來指定當前 class 的 metaclass:

因此,只要我們指定了__metaclass__就可以代替type()創建class.我們自己來寫一個最簡單的metaclass.

class DemoMeta(type):
	pass
class DemoClass(object):
	__metaclass__ = DemoMeta
print type(DemoClass) #<class '__main__.DemoMeta'>

看一個複雜些的例子

class FooMeta(type):
	def __new__(mcs, name, bases, attrs):
		"""
		定製創建 class 的行爲
		作爲示例,這裏將外部傳入的 attrs 的名稱做一些處理:如果以'_'開頭,則轉爲小寫
		:param name: class 名稱
		:param bases: tuple, 父類的名稱
		:param attrs: class attributes
		"""
		converted = {atr if not atr.startswith('_') else atr.lower(): v 
                     for atr, v in attrs.items()}
		cls = super(FooMeta, mcs).__new__(mcs, name, bases, converted)
		return cls


class Foo(object):
    __metaclass__ = FooMeta

python3

py3中,指定元類的語法有一點小小的修改:不再使用 __metaclass__,而是在定義 class 時顯式地指定 metaclass:

class Foo(object, metaclass=CustomMetaclass):
    pass

常見用途

metaclass可以控制類的創建過程,包括類的屬性、方法和父類等。metaclass可以用於實現一些高級的編程技巧,例如自動註冊子類、自動添加屬性和方法等

  1. 統計某種類型
  2. 定義一個單例
  3. 自動添加屬性和方法

如何統計某個類的所有子類

猜想一下,統計某個類的所有子類

__bases__是一個元組,包含了一個類的所有直接父類,所以不不能統計到某種類型

還有一種方法:

使用gc.get_objects()函數獲取所有已經創建的對象,然後使用issubclass()函數判斷一個類是否是另一個類的子類,從而統計所有的子類

以下是一個示例代碼:

import gc

def count_subclasses(cls):
    count = 0
    for obj in gc.get_objects():
        if isinstance(obj, type) and issubclass(obj, cls):
            count += 1
    return count

自動統計某種類型

下面是一個簡單的例子演示瞭如何使用metaclass來自動註冊子類。

假設我們有一個基類Base,我們希望所有繼承自Base的子類都能夠自動註冊到一個全局的字典中。我們可以定義一個Meta類,該類繼承自type,並重寫其__init__方法,在該方法中實現自動註冊的邏輯。然後,我們將Base類的metaclass設置爲Meta類,這樣所有繼承自Base的子類都會使用Meta類來創建實例,並自動註冊到全局字典中。

class Meta(type):
    registry = {}

    def __init__(cls, name, bases, attrs):
        super(Meta, cls).__init__(name, bases, attrs)
        if name != 'Base':
            Meta.registry[name] = cls

class Base(object):
    __metaclass__ = Meta

class Subclass1(Base):
    pass

class Subclass2(Base):
    pass

print Meta.registry

輸出結果爲:

{'Subclass1': <class '__main__.Subclass1'>, 'Subclass2': <class '__main__.Subclass2'>}

可以看到,Subclass1和Subclass2都被自動註冊到了Meta.registry字典中。這樣,我們就可以方便地獲取所有繼承自Base的子類了。

定義單例

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None

    def __call__(cls, *args):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args)
        return cls.instance

class MyCard(object):
	__metaclass__ = Singleton

def testSingle():
	card1 = MyCard()
	card2= MyCard()
	print card1,card2
    #輸出結果:<__main__.MyCard object at 0x03A6FE90> <__main__.MyCard object at 0x03A6FE90>

自動添加屬性和方法

假設我們有一個基類Base,我們希望所有繼承自Base的子類都能夠自動添加一個名爲name的屬性和一個名爲hello的方法。我們可以定義一個Meta類,該類繼承自type,並重寫其__init__方法,在該方法中實現自動添加屬性和方法的邏輯。然後,我們將Base類的metaclass設置爲Meta類,這樣所有繼承自Base的子類都會使用Meta類來創建實例,並自動添加name屬性和hello方法。

class Meta(type):
    def __init__(cls, name, bases, attrs):
        super(Meta, cls).__init__(name, bases, attrs)
        cls.name = name
        cls.hello = lambda self: 'Hello, %s!' % self.name

class Base(object):
    __metaclass__ = Meta

class Subclass1(Base):
    pass

class Subclass2(Base):
    pass

print Subclass1().hello()
print Subclass2().hello()
#Hello, Subclass1!
#Hello, Subclass2!

Python選取 metaclass 的策略

在Python中,當我們定義一個類時,解釋器會根據以下順序來選擇metaclass:

  1. 如果該類顯式指定了metaclass,則使用該metaclass。
  2. 否則,如果該類的父類中有metaclass,則使用該metaclass。
  3. 否則,如果該類的模塊中有metaclass,則使用該metaclass。
  4. 否則,如果該類的基類中有metaclass,則使用該metaclass。
  5. 否則,使用默認的type作爲metaclass。

結尾

如果看完之後你還是看不懂,沒關係,99%的情況下都不需要用到metaclass

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章