美文网首页Python
Python 中 Metaclass 详解

Python 中 Metaclass 详解

作者: mocobk | 来源:发表于2020-07-28 14:51 被阅读0次

内置函数 type() 和 isinstance()

在讲 Metaclass 之前,我们先了解一下几个相关的内置函数:type() 和 isinstance()。

type()

type() 有两个主要的功能:(1)查看一个变量(对象)的类型;(2)创建一个类(class)。

1. 查看一个对象的类型

当传入一个参数时,返回这个对象的类型。

class ClassA:
    name = "type test"

a = ClassA()
b = 3.0

print(type(a))
print(type(b))
print(type("This is string"))
print()

print(a.__class__)
print(b.__class__)

输出结果:

<class 'advanced_programming.Metaclass.ClassA'>
<class 'float'>
<class 'str'>

<class 'advanced_programming.Metaclass.ClassA'>
<class 'float'>

这个时候,通常与 object.__class__ 的功能相同,都是返回对象的类型。

2. 创建一个类

当传入三个参数时,用来创建一个类。

class type(name, bases, dict)
  • name:要创建的类的类型
  • bases:要创建的类的基类,Python 中允许多继承,因此这是一个 tuple 元组的类型
  • dict:要创建的类的属性,是一个 dict 字典类型
ClassVariable = type('ClassA', (object,), dict(name="type test"))
a = ClassVariable()
print(type(a))
print(a.name)

输出结果:

<class 'advanced_programming.Metaclass.ClassA'>
type test

在这段代码中,通过 type('ClassA', (object,), dict(name="type test")) 创建一个类 ClassVariable,再通过 ClassVariable() 创建一个实例 a。通过 type 创建的类 ClassA 和 class ClassA 这种定义创建的类是一样的。

class ClassA:
    name = "type test"

a = ClassA()
print(type(a))
print(a.name)

正常情况下,我们都是用 class Xxx... 来定义一个类;但是,type() 函数也允许我们动态地创建一个类。Python 是一个解释型的动态语言,这是与静态语言(如 Java、C++)的最大区别之处:动态语言可以很方便地在运行期间动态地创建类。

isinstance()

isinstance() 的作用是判断一个对象是不是某个类型的实例,函数原型:

isinstance(object, classinfo)
  • object:要判断的对象
  • classinfo:期望的类型

如果 object 是 classinfo 的一个实例,或 classinfo 子类的一个实例,则返回 True,否则返回 False。

看一个示例:

class BaseClass:
    name = "Base"

class SubClass(BaseClass):
    pass


base = BaseClass()
sub = SubClass()

print(isinstance(base, BaseClass))
print(isinstance(base, SubClass))
print()

print(isinstance(sub, SubClass))
print(isinstance(sub, BaseClass))

输出结果:

True
False

True
True

如果要知道子类与父类之间的继承关系,可用 issubclass() 方法,或 object.__bases__

print(issubclass(SubClass, BaseClass))
print(issubclass(BaseClass, SubClass))
print(SubClass.__bases__)

输出结果:

True
False
(<class 'advanced_programming.Metaclass.BaseClass'>,)

Metaclass

metaclas 直译为元类(我还是更喜欢原名,后文也将不再翻译),可控制类的属性和类实例的创建过程。

在 Python 中,一切都是对象:一个整数是对象,一串字符是对象,一个类实例也一个对象,类本身也是一个对象。 一个类也是一个对象,和其他对象一样,它是元类(Metaclass)的一个实例。我们用一个图来表示对象(obj,或叫实例)、类(class)、元类(Metaclass)的关系。

image

附图3-1 对象、类、元类的关系

我们来看下面的实例:

class MyClass:
    pass

m = MyClass()
print(type(MyClass))
print(type(m))
print()

print(isinstance(m, MyClass))
print(isinstance(MyClass, type))

输出结果:

<class 'type'>
<class 'advanced_programming.Metaclass.MyClass'>

True
True

默认的 metaclas 是 type,所以上面的代码中我们看到 MyClass 的类型是 type 。不幸的是,为了向后兼容,type 这个类型总是让人困惑:type 也可以当作函数来使用,返回一个对象的类型。

造成这种种困扰的始作俑者就是 type,type 在 Python 中是一个极为特殊的类型。为了彻底理解 Metaclass,我们先要搞清楚 type 和 object 的关系。

type 与 object 的关系

在 Python 3 中,object 是所有类的基类,内置的类、自定义的类都直接或间接地继承自 object 类。如果你去看源码,会发现 type 也继承自 object。这就对我们的理解造成极大的困扰,主要表现在以下三点。

  • type 是一个 Metaclass,而且是一个默认的 Metaclass。也就是说,type 是 object 的类型,object 是 type 的一个实例。
  • type 是 object 的一个子类,继承 object 的所以属性和行为。
  • type 还是一个 callable,即实现了 __call__ 方法,可以当成一个函数来使用。

我们用一张图来解释 type 和 object 的关系:

image

附图3-2 type 和 object 的关系

type 和 object 有点像“蛋生鸡”与“鸡生蛋”的关系:type 是 object 的子类,同时 object 又是 type 的一个实例(type 是 object 的类型);二者是不可分离的。

type 的类型也是 type,这个估计更难理解,先这么记着吧!

我们可以自定义 Metaclass,自定义的 Metaclass 必须继承自 type。自定义的 Metaclass 通常以 Metaclass(或 Meta)作为后缀进行取名以示区分(如附图3-2中的 CustomMetaclass),CustomMetaclass 和 type 都是 Metaclass 类型。

所有的类都继承自 object,包括内置的类和用户自定义的类。一般来说类 Class 的类型为 type(即一般的类的 Metaclass 是 type,是 type 的一个实例)。如果要改变类的 Metaclass,必须在类定义时显示地指定它的 Metaclass,如下面的示例:

class CustomMetaclass(type):
    pass

class CustomClass(metaclass=CustomMetaclass):
    pass

print(type(object))
print(type(type))
print()

obj = CustomClass()
print(type(CustomClass))
print(type(obj))

print()
print(isinstance(obj, CustomClass))
print(isinstance(obj, object))

输出结果:

<class 'type'>
<class 'type'>

<class 'advanced_programming.Metaclass.CustomMetaclass'>
<class 'advanced_programming.Metaclass.CustomClass'>

True
True

自定义 Metaclass

自定义 Metaclass 时,要注意以下几个点。

(1)object 的 __init__ 方法只有 1 参数,但自定义 metaclass 的 __init__ 有 4 个参数。

def __init__(self)

但 type 重写了 __init__ 方法,有 4 个参数:

def __init__(cls, what, bases=None, dict=None)

因为自定义 Metaclass 继承自 type,所以重写 __init__ 方法时也要有四个参数。

(2)普通的类,重写 __call__ 方法说明对象是 callable 的。

在 Metaclass 中 __call__ 方法还负责对象的创建,一个对象的创建过程大致是附图 3-3 这样的:

image

附图 3-3 对象实例的创建过程

我们结合实例代码一起看一下:

class CustomMetaclass(type):

    def __init__(cls, what, bases=None, dict=None):
        print("CustomMetaclass.__init__ cls:", cls)
        super().__init__(what, bases, dict)

    def __call__(cls, *args, **kwargs):
        print("CustomMetaclass.__call__ args:", args, kwargs)
        self = super(CustomMetaclass, cls).__call__(*args, **kwargs)
        print("CustomMetaclass.__call__ self:", self)
        return self

class CustomClass(metaclass=CustomMetaclass):

    def __init__(self, *args, **kwargs):
        print("CustomClass.__init__ self:", self)
        super().__init__()

    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls)
        print("CustomClass.__new__, self:", self)
        return self

    def __call__(self, *args, **kwargs):
        print("CustomClass.__call__ args:", args)

obj = CustomClass("Meta arg1", "Meta arg2", kwarg1=1, kwarg2=2)
print(type(CustomClass))
print(obj)
obj("arg1", "arg2")

输出结果:

CustomMetaclass.__init__ cls: <class 'advanced_programming.Metaclass.CustomClass'>
CustomMetaclass.__call__ args: ('Meta arg1', 'Meta arg2') {'kwarg1': 1, 'kwarg2': 2}
CustomClass.__new__, self: <advanced_programming.Metaclass.CustomClass object at 0x02B921B0>
CustomClass.__init__ self: <advanced_programming.Metaclass.CustomClass object at 0x02B921B0>
CustomMetaclass.__call__ self: <advanced_programming.Metaclass.CustomClass object at 0x02B921B0>
<class 'advanced_programming.Metaclass.CustomMetaclass'>
<advanced_programming.Metaclass.CustomClass object at 0x02B921B0>
CustomClass.__call__ args: ('arg1', 'arg2')

图中每一条实线表示具体操作,每一条虚线表示返回的过程,实例对象的整个创建过程大致是以下这样的。

  1. metaclass.__init__ 进行一些初始化的操作,如一些全局变量的初始化。
  2. metaclass.__call__ 进行创建实例,创建的过程中会调用 class 的 __new____init__ 方法。
  3. class.__new__ 进行具体的实例化的操作,并返回一个实例对象 obj(0x02B921B0)。
  4. class.__init__ 对返回的实例对象 obj(0x02B921B0)进行初始化,如一些状态和属性的设置。
  5. 返回一个用户真正需要使用的对象 obj(0x02B921B0)。

原文参考

相关文章

  • Python 中 Metaclass 详解

    内置函数 type() 和 isinstance() 在讲 Metaclass 之前,我们先了解一下几个相关的内置...

  • 面试常考(python)

    Python语言特性 1.Python的函数参数传递 2.元类 metaclass metaclass 允许创建类...

  • interview_python

    Python语言特性1 Python的函数参数传递2 Python中的元类(metaclass)3 @static...

  • metaclass

    深刻理解Python中的元类(metaclass) - 文章 - 伯乐在线

  • Python metaclass/type 原理详解

    在学习metaclass 之前先了解一下 type() 和 isinstance() 函数的用法。 内置函数 ty...

  • Python中的metaclass

    之前一直被Python中的元类困扰,花了点时间去了解metaclass,并记录下我对于元类的理解,这里使用的是Py...

  • Runtime专题

    参考资料 1、iOS Runtime详解 2、iOS runtime 之 Class 和 MetaClass 3、...

  • 一文搞懂什么是Python的metaclass

    Python 有很多黑魔法,为了不分你的心,今天只讲 metaclass。对于 metaclass 这种特性,有两...

  • 详解 python 中的 metaclass 到底是啥

    参考:使用元类 - 廖雪峰 首先 ,metaclass 是一个指定类创建时候的操作,他就像是一个特殊的继承,但是...

  • 理解Python中的元类(metaclass)

    理解Python中的元类(metaclass) 这篇文章基本上是What are metaclasses in P...

网友评论

    本文标题:Python 中 Metaclass 详解

    本文链接:https://www.haomeiwen.com/subject/rnpfrktx.html