当前位置: 首页 > news >正文

python3元类深入解读

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

由于 oschina 的博客不支持 mermaid 的图,可以看看挂在gitee上的静态博客

0. intro

元类是 python 里被说烂了的一个东西,然而日常用到的地方实在不多,每次想到都得查一下谷歌,想想干脆在博客留个笔记好了。

元类的主要用途是定制的产生过程,以便于根据类声明包含的信息来创建出不同的类。

1. type

提到元类不得不说一下 python 的类型系统。

python 的 class 也被视作一个对象,定制一个 class 的构造过程其实就和平时在 class 定义里写__init__没啥区别。

python3 里类的类型是typetype又继承自objectobject的父类是自己,构成一个奇怪的闭环。其中,type本身是一个特殊的类,他是自己的实例。

graph TB;
	type --> |inherite|object;
	type --> |instance-of| type;
	object --> |instance-of|type;
	other-cls --> |instance-of| type;
	other-cls --> |inherite| object;
	other-cls-instance --> |instance-of|other-cls;

type有两种调用方式,一种是最常用的接受一个对象参数,返回该对象的类型,另一种是不怎么常用的,直接创建一个新的类型。

# usage with one argument
type(object) # 返回对象的类型,这里返回的是 `type`

# usage with three arguments
type(name, bases, attr) # 返回新创建的类型

2. meta class

元类语法如下

class MyClass(basecls1, basecls2, metaclass=MetaClass, named1=arg, named2=arg): ...

一般的元类可以是一个真正的class或者一个函数。

以函数为例:

def meta_f(name, bases, attr):
	return type(name, bases, attr)

class A(metaclass=meta_f): ...

以类为例:

class MetaC(type):
	def __new__(mcs, name, bases, attr):
		return type.__new__(mcs, name, bases, attr)

class A(metaclass=MetaC): ...

元类可以接受参数,参数必须是命名的,传递参数的方式是写在类声明的继承列表里。

def meta(name, bases, attr, named_arg, optional_arg=None):
	return type(name, bases, dict(**attr, arg=named_arg, option=optional_arg))

class A(metaclass=meta, named_arg="hi"): ...

print(A.arg)  # output: hi

位置参数都会被当成继承列表,作为bases参数(list)的一部分传入元类。

3. 元类继承规则

有了元类那么就有了相应继承规则,显而易见。元类用于构造一个类,两个父类分别有一个不同的元类显然会造成冲突:这个子类用哪个元类构造?

首先看元类的在创建类的过程中的位置,摘自 python 文档3.3.3.1. Metaclasses

  • MRO entries are resolved
  • the appropriate metaclass is determined
  • the class namespace is prepared
  • the class body is executed
  • the class object is created

一旦处理完继承链(mro, method resolve order)之后,就会决定采用哪个 metaclass 作为构造这个类的元类。

在 python 文档的3.3.3.3 determining the appropriate metaclass中描述了如何确定合适的元类,摘录如下。

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

翻译如下

  • 如果没有基类也没有指定 metaclass,那么type()将作为元类使用。
  • 如果指定了元类,并且该元类不是 type 的实例,那么直接使用这个元类。
  • 如果元类是一个 type 的实例,或者存在基类,那么使用最衍生的元类。

有一个比较难理解的点是

most derived metaclass

也就是所谓的最衍生的元类。惯例,先放文档解释

The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError.

简单翻译如下

最衍生的元类会从类声明中明确提供的元类,还有所有明确继承的基类的元类中选择。最衍生的元类是以上所有候选元类的子类型,如果没有类型符合这一条件,则抛出TypeError异常。

重点在于,最衍生的元类必须是,所有继承的基类的元类和指定元类的子类型

在这里提醒一下,issubclass(cls, cls)的结果是True。换句话说,必须有一个类是所有元类的子类,或者所有基类有相同的元类。

代码举例如下

class MetaA(type):
    def __new__(mcs, name, bases, attr):
        print('MetaA <- '+name)
        return type.__new__(mcs, name, bases, attr)

class MetaB(type):
    def __new__(mcs, name, bases, attr):
        print('MetaB <- '+name)
        return type.__new__(mcs, name, bases, attr)

class BaseA: ...
class BaseB(metaclass=MetaA): ...
class BaseC(metaclass=MetaB): ...

# 未指定元类,基类元类分别是type和type的子类,则选择继承链底部的那个类
class A(BaseA, BaseB): ...  # Ok,元类是 MetaA

# 指定元类,元类和基类元类相同的情况下,元类就是那个元类
class C(BaseB, metaclass=MetaA): ...  # Ok,元类是 MetaA

# 指定元类,元类并不处于继承链底端的情况下,元类选择继承链底端的类
class D(BaseB, metaclass=type): ...  # Ok,元类是 MetaA

# 指定元类,但元类和父类无父子类关系
class E(BaseC, metaclass=MetaA): ...  # TypeError

# 不指定元类,基类具有不同的元类
class F(BaseA,BaseB,BaseC): ...  # TypeError

输出如下

MetaA <- A
MetaA <- C
MetaA <- D

In [71]: class E(BaseC, metaclass=MetaA): ...  # TypeError
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-71-9129a36c52b2> in <module>
----> 1 class E(BaseC, metaclass=MetaA): ...  # TypeError

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

In [72]: class F(BaseA,BaseB,BaseC): ...  # TypeError
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-72-1c510edd69d1> in <module>
----> 1 class F(BaseA,BaseB,BaseC): ...  # TypeError

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

但元类是函数的情况下会有比较特殊的表现,注意规则二。

  • 如果指定了元类,并且该元类不是 type 的实例,那么直接使用这个元类。

如果函数形式的元类作为父类的元类时不会列入选择,除非指定当前类的元类为函数,才会调用函数形式的元类,而且是无条件选择这个函数形式的元类。

def MetaA(name, bases, attr):
    print("MetaA <- "+name)
    return type(name, bases, attr)

class MetaB(type):
    def __new__(mcs, name, bases, attr):
        return type.__new__(mcs, name, bases, attr)

class A(MetaB, metaclass=MetaA): ...  # Ok,无条件选择元类 MetaA

转载于:https://my.oschina.net/u/3888259/blog/2993169

相关文章:

  • 使用podspec创建iOS插件
  • Spring boot 添加日志 和 生成接口文档
  • 生成器13
  • 一个快速检测系统CPU负载的小程序
  • ionic错误
  • java类什么时候加载?,加载类的原理机制是怎么样的?
  • CSS3 transform变换
  • Python-opencv摄像头图像捕获
  • python入门 (一)
  • mysql 多重排序数据顺序可能不固定
  • 单词随记
  • 【346】TF-IDF
  • docker学习笔记-1
  • Qt5.12.0 交叉编译搭建
  • 利用WPF建立自己的3d gis软件(非axhost方式)(十一)SDK中的动画系统
  • Angular 4.x 动态创建组件
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • leetcode讲解--894. All Possible Full Binary Trees
  • ng6--错误信息小结(持续更新)
  • passportjs 源码分析
  • Protobuf3语言指南
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 关于springcloud Gateway中的限流
  • 技术胖1-4季视频复习— (看视频笔记)
  • 判断客户端类型,Android,iOS,PC
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 学习笔记:对象,原型和继承(1)
  • 原生js练习题---第五课
  • 阿里云服务器如何修改远程端口?
  • 关于Android全面屏虚拟导航栏的适配总结
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (1)(1.9) MSP (version 4.2)
  • (8)STL算法之替换
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (一)Linux+Windows下安装ffmpeg
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)visual stdio 书签功能介绍
  • ./configure,make,make install的作用
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • :如何用SQL脚本保存存储过程返回的结果集
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • [ 隧道技术 ] cpolar 工具详解之将内网端口映射到公网
  • [2009][note]构成理想导体超材料的有源THz欺骗表面等离子激元开关——
  • [Android]常见的数据传递方式