深度分析 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

 

posted @ 2021-12-14 13:47  Tracydzf  阅读(183)  评论(0编辑  收藏  举报