背景

最近有一个需求需要自定义一个多继承abc.ABC与django.contrib.admin.ModelAdmin两个父类的抽象子类,方便不同模块复用大部分代码,同时强制必须实现所有抽象方法,没想按想当然的写法实现多继承时,居然报错metaclass conflict:

In [1]: import abc
In [2]: from django.contrib import admin
In [3]: class MyAdmin(abc.ABC, admin.ModelAdmin):
...: pass
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-b159bc04ec1b> in <module>
----> 1 class MyAdmin(abc.ABC, admin.ModelAdmin):
2 pass
3
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

一时之间疑惑满满,先是通过搜索快速找到了一个解决方案,但是却并没有弄明白问题的根本原因与解决方案的原理,最近终于有些时间可以深入探究一番,这里记录一下。

PS: 本文所有讨论均基于Python3,不考虑Python2的部分差异之处。

什么是metaclass(元类)

首先要弄清楚什么是metaclass,才可能明白metaclass conflict的真正含义。

类比普通class与metaclass

这里采用class(类)和instance(实例)的关系来类比解释,如果要创建一个自定义class A,然后创建其实例,一般我们会这么写:

In [1]: class A:
...: def test(self):
...: print('call test')
In [2]: a = A()
In [3]: print(a, type(a))
<__main__.A object at 0x7f9f95414970> <class '__main__.A'>

如上我们自定义了class A,并且生成了class A的实例对象a,print语句的输出可以看出实例a的类型正是class A,此时如果我们进一步探究A的类型会发现:

In [10]: print(type(A))
<class 'type'>

A类型是 class type。

我们会说a是class A的实例,那以此类推可以说class A是class type的实例,或者换一种说法:class A的实例是a,class type的实例是A。

现在我们尝试定义metaclass:

在python中class不仅能创建实例对象,其本身也是一个对象,普通class创建实例普通对象,metaclass(元类)则创建实例class对象。

PS: 严格来说metaclass本身不一定要是一个class,它可以是任意可以返回class的callable对象,这里我们不做深入探讨。

自定义与使用metaclass

在python中应该怎么定义一个metaclass呢,其实type就是一个metaclass,type是所有class的默认metaclass,而且所有自定义的metaclass 最终也都会使用到type来执行最后创建class的工作。

事实上上面使用class A... 的语法定义类A时,Python解释器最终也是调用type来创建的class A,其等价于以下代码:

In [23]: def fn(self):
...: print('call test')
...:
In [24]: A = type('A', (object, ), dict(test=fn))

type创建class的签名如下:

type(name, bases, attrs)
name: 要创建的class名称
bases: 要继承的父类tuple(可以为空,但python3自定义class一般都默认继承object)
attrs: 包含class定义属性名称和值的dict

绝大多数情况下我们并不需要用到metaclass,极少数需要动态创建/修改class的复杂场景比如Django的ORM才需要用到这一技术。这里举一个metaclass简单使用示例,比如我们可以简单创建一个给class统一加上其创建时间的metaclass,以满足需要时可以查看对应class首次创建时间的这个伪需求(仅为举本例而提的需求_),如下AddCTimeMetaclass定义:

In [30]: from datetime import datetime
In [31]: class AddCTimeMetaclass(type):
...: def __new__(cls, name, bases, attrs):
...: attrs['ctime'] = datetime.now()
...: return super().__new__(cls, name, bases, attrs)
...: In [32]: class B(metaclass=AddCTimeMetaclass):
...: pass
...:
In [33]: B.ctime
Out[33]: datetime.datetime(2022, 10, 29, 1, 22, 46, 750176)

在定义class B的时候,通过指定metaclass参数告诉解释器创建class B时不使用默认的type而是使用自定义的元类AddCTimeMetaclass。

metaclass confict(元类冲突)的清晰含义

初步定义了metaclass并了解简单使用之后,我们开始正式探究metaclass conflict,一个最简单触发metaclass conflict的例子如下:

In [42]: class M0(type):
...: pass
...:
In [43]: class M1(type):
...: pass
...:
In [44]: class A(metaclass=M0):
...: pass
...:
In [45]: class B(metaclass=M1):
...: pass
...:
In [46]: class C(A, B):
...: pass
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-46-9900d594feda> in <module>
----> 1 class C(A, B):
2 pass
3 TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

如上M0与M1为自定义metaclass,分别作为A、B的metaclass,当class C试图多继承A、B时就会出问题,从字面意思理解:子类的metaclass必须是其所有基类metaclass的(非严格)子类,看起来普通class的多继承和metaclass的多继承之间发生了什么问题。

这段话具体怎么理解?我们已经知道A、B都分别具有自己的metaclass M0、M1,那么当C多继承A、B的时候C的metaclass应该是M0还是M1呢?由于M0、M1两者之间并没有继承关系,用哪个都不行,Python不知道怎么办,只能告诉你出问题了。

解决方案

那理想情况下C的metaclass到底应该是什么呢?理想情况应该如下所示:

M0     M1
: \ / :
: \ / :
A M2 B
\ : /
\ : /
C

即采用多继承了M0、M1的M2作为C的metaclass,这也是解决这个问题的最终方案,具体代码如下:

In [58]: class M2(M0, M1):
...: pass
...:
In [59]: class C(A, B, metaclass=M2):
...: pass
...:

如上我们通过手动定义M2,并手动明确指定class C的metaclass为M2,如此解决metaclass conflict问题。

这时再回到开头碰到的多继承abc.ABC与admin.ModelAdmin时遇到的问题就很容易理解了:因为abc.ABC有自己的metaclass abc.ABCMeta,同时modelAdmin也有自己的metaclass django.forms.widgets.MediaDefiningClass,并且这两者之间没有继承关系,因而 class MyAdmin(abc.ABC, admin.ModelAdmin) 多继承时解释器无法推断出满足条件的metaclass,自然也就报错了,解决办法和上面的方案一样,定义一个两者metaclass的子类并将其指定为MyAdmin的metaclass即可,代码如下:

In [112]: print(type(abc.ABC), type(admin.ModelAdmin))
<class 'abc.ABCMeta'> <class 'django.forms.widgets.MediaDefiningClass'>
In [113]: class MyMeta(type(abc.ABC), type(admin.ModelAdmin)):
...: pass
...:
In [114]: class MyAdmin(abc.ABC, admin.ModelAdmin, metaclass=MyMeta):
...: pass
...:
In [115]: print(type(MyAdmin))
<class '__main__.MyMeta'>

转载请注明出处,原文地址:https://www.cnblogs.com/AcAc-t/p/python_metaclass_conflict_study.html

参考

https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python

https://www.cnblogs.com/JetpropelledSnake/p/9094103.html

http://www.phyast.pitt.edu/~micheles/python/metatype.html

Python 多重继承时metaclass conflict问题解决与原理探究的更多相关文章

  1. python多重继承C3算法

    python多重继承的MRO算法选择: 经典方式.Python2.2 新式算法.Python2.3 新式算法(C3).Python 3中只保留了最后一种,即C3算法 C3算法的解析: 1.多继承UML ...

  2. Python多重继承之菱形继承

    继承是面向对象编程的一个重要的方式,通过继承,子类就可以扩展父类的功能.在python中一个类能继承自不止一个父类,这叫做python的多重继承(Multiple Inheritance ). 语法 ...

  3. Python描述符以及Property方法的实现原理

    Python描述符以及Property方法的实现原理 描述符的定义: 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实了__get__(),__set__(),__delete__()中 ...

  4. Python获取时光网电影数据

    Python获取时光网电影数据 一.前言 有时候觉得电影真是人类有史以来最伟大的发明,我喜欢看电影,看电影可以让我们增长见闻,学习知识.从某种角度上而言,电影凭借自身独有的魅力大大延长了人类的&quo ...

  5. python自动化测试(2)-自动化基本技术原理

    python自动化测试(2) 自动化基本技术原理 1   概述 在之前的文章里面提到过:做自动化的首要本领就是要会 透过现象看本质 ,落实到实际的IT工作中就是 透过界面看数据. 掌握上面的这样的本领 ...

  6. Python写地铁的到站的原理简易版

    Python地铁的到站流程及原理(个人理解) 今天坐地铁看着站牌就莫名的想如果用Python写其工作原理 是不是很简单就小试牛刀了下大佬们勿喷纯属小弟个人理解 首先来看看地铁上显示的站牌如下: 就想这 ...

  7. 装python package 时,conda提示会升级python2到python3,那可能是你的windows不支持py2env下的此包。

    装python package 时,conda提示会升级python2到python3, 那可能是你的windows不支持py2env下的此包.比如:win 下,tensorflow就不支持py2的环 ...

  8. Python之异常处理(执行python文件时传入参数)

    使用sys模块 使用sys模块里的argv参数,用来保存参数值 import sys #sys.argv的作用是获取到运行python文件时,传入的参数 #默认如果运行python文件不传参数,arg ...

  9. 运行python文件时出错SyntaxError: Non-UTF-8 code starting with '\xb5' in file, but no encoding declared;

    今天ytkah在运行python文件时出现错误,提示如下,很明显这是没有定义python文件编码引起的问题,那么要怎么解决呢?很简单,在文件头部定义一下就可以了. File "hello.p ...

随机推荐

  1. 如何在win下安装dlib的whl文件(Anaconda方式)

    问题描述 由于作业需要用到dlib的人脸检测函数,所以尝试安装了一下dlib.顺便贴上dlib的下载网址dlib下载. 但当我直接输入pip install dlib-19.7.0-cp36-cp36 ...

  2. java单线程100%利用率

    容器内就获取个cpu利用率,怎么就占用单核100%了呢 背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的 目前这个bug已经合入到了开源社区, 链接为 https://git ...

  3. 内网渗透之vlunstack靶场

    前言:vlunstack靶场是由三台虚拟机构成,一台是有外网ip的windows7系统(nat模式),另外两台是纯内网机器(外网ping不通),分别是域控win2008和内网主机win2003,这里就 ...

  4. NOI 2019 省选模拟赛 T1【JZOJ6082】 染色问题(color) (多项式,数论优化)

    题面 一根长为 n 的无色纸条,每个位置依次编号为 1,2,3,-,n ,m 次操作,第 i 次操作把纸条的一段区间 [l,r] (l <= r , l,r ∈ {1,2,3,-,n})涂成颜色 ...

  5. 【Java】idea同时运行多个一样的类

    点击"Edit Configurations..." 在左侧选中需要重复运行的类 单击"Modify options" 选择"Allow multip ...

  6. VS2019 Community社区版登录提示:我们无法刷新此账户的凭证 解决方法

    最正确的方式: 1.点击 帮助-->发送反馈-->报告问题 2.点击 检查新的许可证 ,即可登陆成功 3.如果提示:无法下载或者下载失败. 4.那么就需要在左边 账户选项 中将 嵌入式We ...

  7. 【lwip】005-lwip内核框架剖析

    目录 前言 5.1 lwip初始化 5.2 内核超时 5.2.1 内核超时机制 5.2.2 周期定时机制 5.2.3 内核超时链表数据结构 5.2.4 内核超时初始化 5.2.6 超时的溢出处理 5. ...

  8. Typora 最后免费版本也不能用了?简单一招搞定

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 Typora是一款优秀的 Markdown 编辑 ...

  9. KingbaseES V8R6C5B041手工创建集群测试案例

    ​ 案例说明: KingbaseES V8R6C5B041版本和以前的KingbaseES R6有一定的区别,增加了"securecmdd"的工具,并且在install.conf配 ...

  10. Markdown Support

    Markdown 支持一览 Markdown 支持一览 身正不怕影子斜 我实在没有说过这样一句话 -- 鲁迅 古代文学史发展脉络 唐诗 宋词 元曲 冯·诺依曼结构 运算器 控制器 存储器 输入输出设备 ...