深度分析 Python 元类 abc
元类背景
我们先看 Python 下实现单例模式的一种写法:
class Singleton(type): def __init__(cls, *args, **kwargs): cls._instance = None super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__call__(*args, **kwargs) return cls._instance
class Test(metaclass=Singleton): def test(self): print("test") test1 = Test() test2 = Test() print(id(test1), id(test2))
测试结果,很明显功能是实现了,两次实例化对应的对象为同一个:
>>> test1 = Test() ... test2 = Test() >>> test1 <__main__.Test object at 0x10aa13d68> >>> test2 <__main__.Test object at 0x10aa13d68> >>> id(test1) 4473306472 >>> id(test2) 4473306472
没错,这种方式就是用元类(metaclass)控制类的实例化。
元类的秘密
通过元类实现单例模式我们已经对元类有个基本的认识,下面我们就看看元类到底是什么?
1. 初识元类
type 两种使用方式:
type(object)
获取对象类型。type(name, bases, dict)
创建类对象。
>>> class A(object): ... pass ... >>> A <class '__main__.A'> >>> A() <__main__.A object at 0x108a4ac50> >>> type("B", (object, ), dict()) <class '__main__.B'> >>> type("B", (object, ), dict())() <__main__.B object at 0x108a0eba8>
上面的例子可以发现 type 可以初始化类对象,功能与类定义的方式一致。所以 type(元类)是可以实现类/对象初始化。也就是说:“元类就是创建类的类”。
>>> type(int) <class 'type'> >>> type(str) <class 'type'> >>> type(bytes) <class 'type'>
Python 的基础数据类型的类型都指向 type。在 Python中,内建的类type
是元类。
Python中 定义新的元类是通过向类定义提供关键字参数 metaclass
就可以使用这个新的元类。就像下面一样定义与使用元类:
class NewType(type): pass class A(metaclass=NewType): pass
所以在 Python 中我们一定要区分 object
与 type
。两者不是一个东西却又有着千丝万缕的联系。
下面可以看一下 wiki 中的一个很有意思的例子:
r = object c = type class M(c): pass class A(metaclass=M): pass class B(A): pass b = B()
最重要的就是:
object
类是所有类的祖先。type
元类是所有元类的祖先。
也就是说所有对象(包括 type )都是继承自 object
。所有对象(包括 object )的类型都源与 type (元类)。如下代码所示:
>>> type(object) <class 'type'> >>> isinstance(object, type) True
最后再贴一张图理解一下这两者的关系:
2. 元类的作用
元类可以干预类的创建。 比如 Python 标准库库中就有一个元类 abc.ABCMeta
,该元类的作用可以定义抽象类,类似 Java 中 abstract class
。下面我们就看看它的使用:
class Base(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Http(Base, abc.ABC): pass
我们测试一下代码:
>>> Base() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: Can't instantiate abstract class Base with abstract methods read, write >>> Http() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: Can't instantiate abstract class Http with abstract methods read, write
发现抽象类是不能实例化的。子类必须实现抽象方法才能对类进行实例化,我们修改子类代码:
>>> class Http(Base, abc.ABC): ... ... def read(self): ... pass ... ... def write(self): ... pass ... ... def open(self): ... print(" open method ") ... >>> Http().open() open method
可以看到 Http
继承 Base
并且实现抽象方法是没有任何问题的。
3. 自定义元类
如下在实现对类对象的缓存的功能。
创建一个类的对象时,如果之前使用同样参数创建过这个对象,那就返回它的缓存引用。
import weakref class Cached(type): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__cache = weakref.WeakValueDictionary() def __call__(self, *args): if args in self.__cache: return self.__cache[args] else: obj = super().__call__(*args) self.__cache[args] = obj return obj # Example class Spam(metaclass=Cached): def __init__(self, name): print('Creating Spam({!r})'.format(name)) self.name = name
验证一下功能:
>>> a = Spam('Guido') Creating Spam('Guido') >>> b = Spam('Diana') Creating Spam('Diana') >>> c = Spam('Guido') # Cached >>> a is b False >>> a is c # Cached value returned True
下面看一下普通方式是如何实现的:
import weakref _spam_cache = weakref.WeakValueDictionary() class Spam: def __init__(self, name): self.name = name def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s