关于Python 多重继承时metaclass conflict问题解决与原理探究
作者:及时 发布时间:2022-04-17 04:50:10
背景
最近有一个需求需要自定义一个多继承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.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
来源:https://www.cnblogs.com/AcAc-t/p/python_metaclass_conflict_study.html


猜你喜欢
- 配置环境:redhat6.5server1:redis(172.25.254.1)server2:php(172.25.254.2)serv
- PIL图片如何按比例裁剪问题描述如图片比例为 1:1 裁剪为 4:31.jpg解决方案from PIL import Imagedef im
- 前言之前在进行深度学习训练的时候,偶然发现使用PIL读取图片训练的效果要比使用python-opencv读取出来训练的效果稍好一些,也就是训
- 本文实例讲述了Python连接MongoDB数据库的方法。分享给大家供大家参考,具体如下:Python使用pymongo操作MongoDB数
- 编者按,网站中让人惊喜的往往是那一点细节,只要用心留意你将发现那些美好的用户体验就在身边。新蛋网想自主控制链接在原窗口还是新窗口中打开?看看
- 准备工作去年的时候,青云发表过关于栅格系统的文章 . 我们知道网页的栅格系统是运用固定的格子设计版面布局,使其风格工整简洁. 视觉上来说,栅
- 说到装饰器,就不得不说python自带的三个装饰器:1、@property 将某函数,做为属性使用@property 修饰,就是将方法,变成
- XML 是严格又自由的标记语言。我们都习惯于它的自由特性,自己想怎么定义都行,设计上非常自由,从不会因为它的标记特性约束到设计灵感的发挥。对
- 问题:cudatoolkit cudnn 通过conda 虚拟环境安装,先前已经使用virtualenv安装tf,需要在conda虚拟环境中
- 通信方式进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块主要通过队列方式队列:队列类似于一条管道,元素先
- 我们很多时候,特别是在生成任务的时候,都需要一个唯一标识字符串来标识这个任务,比较常用的有生成uuid或者通过时间来生成。uuid的话可以直
- 题目:用 JavaScript 代码实现空位补零,比如 pad(12, 3) => 012实现一:/* 平淡无奇法 */functio
- 0 引言上周被一则新闻震惊到了,《2454万元大奖无人认领!福彩史上第二大弃奖在广东中山产生 》,在2019年5月2日开奖的双色球中,广东中
- 效果图实例代码如下:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transit
- GoLang之使goroutine停止的5种方法1.goroutine停止介绍goroutine是Go语言实现并发编程的利器,简单的一个指令
- 1.已知有一个XML文件(bookstore.xml)如下: <?xml version="1.0" e
- 我们知道numpy.ndarray.reshape()是用来改变numpy数组的形状的,但是它的参数会有一些特殊的用法,这里我们进一步说明一
- 今天在项目中,使用Mybatis对oracle数据库进行操作的时候,报出ORA-00911: invalid character的错误,检查
- 一般打印就用window.print();就OK了 但是一般都是选择性打印所以会调用方法: function preview() { bdh
- 📚引言泰坦尼克号的沉没是历史上最惨痛的沉船事件之一。1912年4月15日,泰坦尼克号在其处女航中与冰山相撞后沉没,2224名乘客和船员中的1