背景

最近有一个需求需要自定义一个多继承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. Docker 10 镜像原理

    参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...

  2. iommu分析之---smmu v3的实现

    smmu 除了完成 iommu 的统一的ops 之外,有自己独特的一些地方. 1.Stream Table Stream Table是存在内存中的一张表,在SMMU设备初始化的时候由驱动程序创建好. ...

  3. Sentinel控制台1.8.3修改源码,修改配置后推送到Nacos

    目录 1. 接着上一篇 2. 思路 3. 下载Sentinel源码 4. 看Gateway里面读取的配置信息 5. 修改Sentinel控制台源码 6. 熔断规则测试 7. 限流规则测试 8. 打包使 ...

  4. SQL order by 语句对null值排序

    记order by 语句对null值排序: 目录 记order by 语句对null值排序: MySQL: Oracle: SqlServer: MySQL: 将null值放在最后 select * ...

  5. luogu [ZJOI2007] 矩阵游戏

    [ZJOI2007] 矩阵游戏 题目描述 小 Q 是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个 \(n \times n\) 黑白方阵进行(如同国际象棋 ...

  6. KingbaseES触发器介绍

    触发器及其作用 触发器(trigger)是用户定义的由事件驱动的特殊过程.一旦定义,所有用户的对应操作均会由服务器自动激活相应的触发器,在DBMS核心层进行集中的完整性控制. 触发器类似于约束,但是比 ...

  7. 001从零开始入门Entity Framework Core——基础知识

    Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台版的常用 Entity Framework 数据访问技术. 一.什么是 Entity Framework Core ...

  8. Redis6.0.6的三大内存过期策略和八大淘汰策略

    一.前言 Redis在我们日常开发中是经常用到的,Redis也是功能非常强大,可以进行缓存,还会有一些排行榜.点赞.消息队列.购物车等等:当然还有分布式锁Redisson,我们使用肯定少不了集群!小编 ...

  9. Kafka为什么性能这么快?4大核心原因详解

    Kafka的性能快这是大厂Java面试经常问的一个话题,下面我就重点讲解Kafka为什么性能这么快的4大核心原因@mikechen 1.页缓存技术 Kafka 是基于操作系统 的页缓存(page ca ...

  10. Bugly iOS自动导入符号表

      前言       最近在处理Bugly问题的时候顺便解决了下符号表上传的问题,使用最新的上传工具包,也是顺便整理了下可以使用的脚本添加到了项目中,把这个过程中遇到的问题总结出来,脚本也会给出来,实 ...