Python 26 面向对象进阶 元类

Python 26 面向对象进阶 元类

旧博客Python系列

元类

类也是一个对象,类究竟是从什么地方产生的呢. 类其实就是从元类产生的,每个类,就是元类的一个实例.(注意,和继承不同) 元类是用来控制如何创建类的,是类的模板,元类的实例就是类. 定义一个类,然后显示类名.__class__可以看到,类的类是type type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象 type的使用方法:
F2= type('Fooo',(object,),{'x':1,'y':'test'})

f1 = F2()

type 接收三个参数:第 1 个参数是字符串 ‘Foo’,表示类名;第 2 个参数是元组 (object, ),把要继承的类放到这个元组里;第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法.如果有属性和方法,都可以放入该字典.

定制自己的元类并且通过metaclass指定元类

可以通过继承type类来定制一个自己的元类,然后通过自己的元类生成类.
class Mytype(type):
    def __init__(self):
        pass


class Foo(metaclass=Mytype):
    def __init__(self,name):
        self.name = name

结果发现报错 TypeError: init() takes 1 positional argument but 4 were given.

这是因为在Foo类里指定元类的时候,由于元类还不存在,所以触发了Mytype的init方法,结果说我们写在Mytype里的init方法只接受了一个参数但是给过来4个,不过现在不知道给过来的是什么,那就先设置4个参数然后打印一下看看吧

class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)


class Foo(metaclass=Mytype):  # 就像执行了Foo = Mytype('Foo',(),{...})
    def __init__(self,name):
        self.name = name

结果分别是:

<class '__main__.Foo'>
Foo
()
{'__module__': '__main__', '__qualname__': 'Foo', '__init__': <function Foo.__init__ at 0x000001C6BE8F60D0>}

在指定元类的时候发生了什么,可见self是Foo类,传了进来,因为类相当于元类的实例.之后是Foo的名称,一个元组,一个字典.猜想这里肯定是和type的参数一样.也就是指定元类的类,在生成自己的时候,会把自己的参数交给元类来实例化出自己这个类.

继续分析,来生成一个Foo的实例:

f1 = Foo('test')  # 这是在调用Foo,也就是call Foo
# call的方法是在上层定义的,所以Foo的call方法是来自于Foo的元类,也就是Mytype.__call__.

但是先在Mytype里没有定义__call__,所以这个方法其实来自于type类.如果我们自己定义一个__call__方法来看看.

class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)

    def __call__(self, *args, **kwargs):
        return 

class Foo(metaclass=Mytype):
    def __init__(self,name):
        self.name = name

f1 = Foo('test')  # 这是在调用Foo,也就是call Foo
print(f1.name) # 报错,AttributeError: 'NoneType' object has no attribute 'name'

由于我们自定义的call方法返回了None,这个时候会发现,f1实例化的过程就是调用Mytype的call,结果返回None,则f1就是None.
实例化的过程就是把Foo类和带有的参数都传给了__call__方法.来试验一下:

# Foo()实例化就是调用Foo的元类的__call__方法
class Mytype(type):
    def __init__(self, a, b, c):
        pass

    def __call__(self, *args, **kwargs):
        print(self)
        print(args, kwargs)
        return


class Foo(metaclass=Mytype):
    def __init__(self, name):
        self.name = name


f1 = Foo('test')  # 这是在调用Foo,也就是call Foo

结果是:

<class '__main__.Foo'> # self,是把Foo类作为元类的实例穿进去
('test',) {} # 这些就是Foo()调用时括号内的参数
那么至此就明白了,元类的__call__方法需要返回一个子类的对象.

class Mytype(type):
    # def __init__(self, a, b, c):
    #     print(a)

    def __call__(cls, *args, **kwargs):
        obj = object.__new__(cls)   # 利用内置的所有类继承的类object.__new__方法来生成一个Foo类的空对象
        cls.__init__(obj, *args, **kwargs)  # 这一步很关键,Foo的初始化方法就是给Foo对象加上属性的方法,所以这里,要把obj给弄到Foo的init方法里过一下,这个对象才能带上属性
        return obj

class Foo(metaclass=Mytype):
    def __init__(self, name):
        self.name = name


f1 = Foo('test')  # 这是在调用Foo,也就是call Foo
print(f1.name)

这就是元类的奥秘,调用类生成实例,就是调用类的元类的__call__方法生成一个这个类的对象交给了变量名. 如果去掉类与对象的概念,用一切皆对象来划分的话,元类-类-对象分别是一级,二级,三级对象,想调用某一个级别的对象,就要在高一级的对象上弄一个call方法.

关于元类,有Stack Overflow上有一个很好的解释:What are metaclasses in Python?,对应的中文翻译.

元类的玩法已经是python里的高级玩法了,这个回答总结的很棒:
Indeed, metaclasses are especially useful to do black magic, and therefore complicated stuff. But by themselves, they are simple:

intercept a class creation # 截获类的创建过程
modify the class # 定制类
return the modified class # 返回修改过的类

LICENSED UNDER CC BY-NC-SA 4.0
Comment