前言

定义class是为instance编程,而定义metaclass是为class编程。


type(object)的结果是什么?

值得注意的是,在Python中,所有东西都是object, 使用type(something)可以查看该对象的类。比如:

>>> print(type(1))
<class 'int'>
>>> print(type('abc'))
<class 'str'>

一个整数就是一个int类,一个字符串就是一个str类。既然一切东西都是类,那它们都包含属性,方法,是从其它对象继承而来的,也可以被继承。举一个简单的例子:

>>> class MyStr(str):
...     def __add__(self, other):
...         print("MyStr is adding...")
...         return super().__add__(other)
...
>>> MyStr('abc') = 'd'
>>> MyStr('abc') + 'd'
'abcd'

这里通过继承str实现了新的类MyStr,并且重写了MyStr加法计算的方法。可以使用type来观察这些变量的类型:

>>> mystr = MyStr('abc')
>>> print(type(mystr))
<class '__main__.MyStr'>
>>> print(type(MyStr))
<class 'type'>
>>> print(type(str))
<class 'type'>
>>> print(type(type))
<class 'type'>

通过上面的代码可以发现,实例化的对象mystr的type是MyStr,而类本身呢?未经过实例化的class本身呢?它的typetype!总结来说,实例化的对象的type是class,而class本身的type是typetype本身的type还是type一切都是从type开始衍生而来。python的这种表现是在python语言底层实现的时候构建的。


使用type动态创建类

type的另一个作用是动态创建类。下面的两段代码的作用是一样的:

class Foo(object):
	spam = 1
	
class Bar(Foo):
	def get_spam(self):
		return self.spam
		
bar = Bar()
print(bar.get_spam())
Foo = type('Foo', (), dict(spam=1))
bar = type('Bar', (Foo,), dict(get_spam=lambda self: self.spam))
bar = Bar()
print(bar.get_spam())

type这里作为一个生产class的工厂,它的作用就像下面的class_factory,能够产出或者说返回class:

def class_factory():
	class Foo(object):
		pass
	return Foo
Foo = class_factory()
foo = Foo()
print(type(foo))

这里的class_facotry可以称为metafunction。它能够产出的是class,而不是实例!与它作用相同的type,同样产出class,所以可以叫做metaclass所以,所谓的元编程,可以理解为对产出class的类或者函数编程。这些类也就叫做元类,元函数啦。 需要注意的是:type只有再输入三个参数的时候产出class,当输入一个参数的时候type的作用是返回参数的type。三个参数定义为:

  • name: string类型,表示将要构建的类的名字
  • bases: tuple类型,表示构建的类要从哪些类继承
  • dct: dict类型,表示构建的类包含那些特征和方法

创建自己的metaclass,从改造type开始

通过从type继承并扩展,就可以自定义metaclass的行为。 先看一个简单的例子:

class MyInt(type):
    def __call__(cls, *args, **kwargs):
        print("***** Here's My int *****", args)
        print("Now do whatever you want with these objects...")
        return type.__call__(cls, *args, **kwargs)
		
class int(metaclass=MyInt):
	def __init__(self, x, y):
		self.x = x
		self.y = y
		
i = int(4, 5)
# ***** Here's My int ***** (4, 5)
# Now do whatever you want with these objects...

对于已经存在的类来说,当需要创建对象时,将调用python的特殊方法__call__,这段代码中,当我们使用int(4, 5)来实例化int类的时候,MyInt元类的__call__方法将被调用,这意味着现在元类控制着对象的实例化。

再来看一个例子,该例子可以创建一个API,用户可以在它之中创建一个文件对象的接口,并且创建一个string ID。首先需要创建metaclass接口,从type中继承:

class InterfaceMeta(type):
    def __new__(cls, name, parents, dct):
        # create a class_id if it's not specified
        if 'class_id' not in dct:
            dct['class_id'] = name.lower()
        
        # open the specified file for writing
        if 'file' in dct:
            filename = dct['file']
            dct['file'] = open(filename, 'w')
        
        # we need to call type.__new__ to complete the initialization
        return super(InterfaceMeta, cls).__new__(cls, name, parents, dct)

它会为class添加一个class_id,并将输入参数中的字符串替换为文件对象。然后,要使用这个InterfaceMete元类来创建Interface类:

Interface = InterfaceMeta('Interface', (), dict(file='tmp.txt'))

print(Interface.class_id)
print(Interface.file)
# interface
# <open file 'tmp.txt', mode 'w' at 0x21b8810>

上面的代码和下面的代码等效,只不过又换回了原来的缩进风格:

class Interface(object, metaclass=InterfaceMeta):
    file = 'tmp.txt'
    
print(Interface.class_id)
print(Interface.file)

通过定义metaclass,构建Interface类的时候python就知道去使用InterfaceMeta而不是type。输入:

type(Interface)
# __main__.InterfaceMeta

可以发现python按我们期望的那样工作着。此外,所有从Interface继承的类也会使用同样的metaclass来构建:

class UserInterface(Interface):
	file = 'foo.txt'
	
print(UserInterface.file)
print(UserInterface.class_id)

这个例子展示了如何使用metaclass来为项目创建强大灵活的API。Django项目使用这些构造来允许对其基本类的强大扩展的简明声明。


另一个例子,注册所有子类

metaclass的另一种可能用途是自动注册从给定基类派生的所有子类。例如,您可能拥有数据库的基本接口,并希望用户能够定义自己的接口,接口将自动存储在主注册表中。

class DBInterfaceMeta(type):
    # we use __init__ rather than __new__ here because we want
    # to modify attributes of the class *after* they have been
    # created
    def __init__(cls, name, bases, dct):
        if not hasattr(cls, 'registry'):
            # this is the base class.  Create an empty registry
            cls.registry = {}
        else:
            # this is a derived class.  Add cls to the registry
            interface_id = name.lower()
            cls.registry[interface_id] = cls
            
        super(DBInterfaceMeta, cls).__init__(name, bases, dct)

在metaclass中添加了一个registry,在添加完registry后,新的类会被添加到其中。

class DBInterface(object, metaclass=DBInterfaceMeta):
    pass
    
print(DBInterface.registry)
# {}
class FirstInterface(DBInterface):
    pass

class SecondInterface(DBInterface):
    pass

class SecondInterfaceModified(SecondInterface):
    pass

print(DBInterface.registry)
# {'firstinterface': <class '__main__.FirstInterface'>, 'secondinterface': <class '__main__.SecondInterface'>, 'secondinterfacemodified': <class '__main__.SecondInterfaceModified'>}

什么时候需要metaclass?

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

– Tim Peters

这段大意是:在确实需要metaclass的时候,需要它的用户自然就会想到它。暂时我还不需要。


参考链接