python中的metaclass可謂熟悉而又陌生,自己開發時很少用,閱讀源碼時卻經常遇到,那麼到底什麼是metaclass呢?何時使用metaclass呢?
動態創建class的方法
假設我們需要動態創建一個class,那麼一般我們有這樣幾種方法
- 通過一個函數動態創建class
- 通過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可以用於實現一些高級的編程技巧,例如自動註冊子類、自動添加屬性和方法等
- 統計某種類型
- 定義一個單例
- 自動添加屬性和方法
如何統計某個類的所有子類
猜想一下,統計某個類的所有子類
__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:
- 如果該類顯式指定了metaclass,則使用該metaclass。
- 否則,如果該類的父類中有metaclass,則使用該metaclass。
- 否則,如果該類的模塊中有metaclass,則使用該metaclass。
- 否則,如果該類的基類中有metaclass,則使用該metaclass。
- 否則,使用默認的type作爲metaclass。
結尾
如果看完之後你還是看不懂,沒關係,99%的情況下都不需要用到metaclass