GochenRyan的博客

往者不可谏,来者犹可追。

0%

metaclass

分析了metaclass的原理,并使用其来实现单例。

问题导读

  • [ ] __new__、__init__、__call__有啥联系与区别?
  • [ ] 元类是什么?
  • [ ] 原理是什么?
  • [ ] 怎么使用?
  • [ ] 怎么用metaclass实现单例?

__new__、__init__、__call__

先了解一下这几个前置知识点,后面会用到。

  • __new__: 用于创建对象并返回对象,当返回对象时会自动调用init方法进行初始化
  • __init__: 在对象创建好之后初始化变量
  • __call__:
    • 只要一个对象对应的class对象中实现了“__call__”操作,那么这个对象就是一个可调用的对象
    • 所谓调用,就是执行对象的type所对应的class对象的tp_call操作
    • 类也是对象,元类(metaclass)是用来创建类(对象)的可调用对象,在元类里重写call,就可以在实例化对象的时候搞事情

元类

元类(metaclass)是用来创建类的可调用对象。对于实例对象、类和元类,可以用下面的图来描述:
metaclass1

那么,元类有什么用呢?看看下面的例子。

  • 在 Python2 中,我们只需在 Foo 中加一个__metaclass__的属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class CPrefixMetaClass(type):
    def __new__(cls, name, bases, attrs):
    _attrs = (("my_" + name, value) for name, value in attrs.items())
    _attrs = dict((name, value) for name, value in _attrs)
    return type.__new__(cls, name, bases, _attrs)

    class CTest(object):
    __metaclass__ = CPrefixMetaClass
    name = "test"

    test = CTest()
    print test.my_name
  • 在 Python3 中,这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class CPrefixMetaClass(type):
    def __new__(cls, name, bases, attrs):
    _attrs = (("my_" + name, value) for name, value in attrs.items())
    _attrs = dict((name, value) for name, value in _attrs)
    return type.__new__(cls, name, bases, _attrs)

    class CTest(metaclass=CPrefixMetaClass):
    name = "test"

    test = CTest()
    print(test.my_name)

结果:

1
2
F:\PyProject\untitled\venv\Scripts\python.exe F:/PyProject/untitled/metaclass.py
test

CPrefixMetaClass继承type,这是因为CPrefixMetaClass是用来创建类的__new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:

  • cls:当前准备创建的类
  • name:类的名字
  • bases:类的父类集合
  • attrs:类的属性和方法,是一个字典

可以直接使用type来动态创建类,不过在项目中没怎么用到,这里提及一下。感兴趣的可以搜一下,很简单。

使用元类来创建单例

元类定义call方法,可以抢在类运行 __new__ 和 __init__ 之前执行,也就是创建单例模式的前提,在类实例化前拦截掉。type的call实际上是调用了type的newinit
metaclass2

单例的实现代码

singleton.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding: utf-8 -*-
g_lSingletonCls = []

def Reset():
global g_lSingletonCls
for oCls in g_lSingletonCls:
oCls.ClearInstance()


class Singleton(type):
def __new__(cls, *args, **kwarg):
print "Singleton.__new__"
return type.__new__(cls, *args, **kwarg)

def __init__(cls, name, bases, dct):
print "Singleton.__init__"
super(Singleton, cls).__init__(name, bases, dct)
cls.instance = None

def __call__(cls, *args, **kwargs):
print "Singleton.__call__"
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
global g_lSingletonCls
g_lSingletonCls.append(cls)
return cls.instance

def ClearInstance(cls):
print "Singleton.ClearInstance"
cls.instance = None

def HasInstance(cls):
return cls.instance != None


class CCase(object):
__metaclass__ = Singleton
def __init__(self):
print "CCase.__int__"

def __new__(cls, *args, **kwargs):
print "CCase.__new__"
return object.__new__(cls)

test_singleton.py

1
2
3
4
5
6
7
8
9
#-*- coding:utf-8

import singleton

oCase = singleton.CCase()
print "before\n", singleton.CCase.HasInstance()

singleton.CCase.ClearInstance()
print "after\n", singleton.CCase.HasInstance()

结果:

metaclass3

参考链接

https://wiki.jikexueyuan.com/project/explore-python/Class/metaclass.html
https://zhuanlan.zhihu.com/p/98440398

写在最后

想要知道更多底层的细节,建议阅读《Python源码剖析》第十二章,那里有你想要的答案。