Python 元类中的继承是如何工作的?

如何解决Python 元类中的继承是如何工作的?

假设我有一个自定义元类和一个链接到它的类:

class Meta(type): pass
class A(metaclass=Meta): pass

据我了解,在 class A 语句的末尾,执行了以下步骤:

  1. 致电Meta('A',(),{})
  2. 因为第 1 步是一个内置调用,这意味着 type.__call__(...) 将被调用。这是因为 type 链接到 Meta.__class__
  3. type.__call__(...) 依次运行另外两个方法(__new____init__)。
  4. 如果 Meta 定义了这些方法中的一个或两个,那么在 type.__call__ 中,这些方法将作为 Meta.__new__(...) 和/或 Meta.__init__(...) 被调用。
  5. A 已创建并链接到 Meta (A.__class__)。

现在,假设我有一个 A 的子类:

class Meta(type): pass
class A(metaclass=Meta): pass
class B(A): pass

class B 语句的末尾,以下步骤是否正确?

  1. 调用 type('B',{}) 而不是 Meta,因为 B.__class__type
  2. 调用 type.__call__(...),后者又会运行另外两个方法(__new____init__)。
  3. type.__new__(type,'B',(A,),{})
  4. type.__init__(cls,{})

假设上述步骤是正确的(我对此表示怀疑),难道 B.__class__ 不应该给出 type 而不是 Meta?我的理由是 B 是使用默认 type 元类创建的。但是打印出 B.__class__ 给出的是 Meta 而不是 type

print(B.__class__) #<class '__main__.Meta'>

此外,如果我手动创建一个以 A 作为父类的类,创建的类也会链接到 Meta

C = type.__call__(type,'C',{})
print(C.__class__) #<class '__main__.Meta'>

#or

D = type.__new__(type,'D',{})
print(D.__class__) #<class '__main__.Meta'>

我的问题是 Python 如何创建 class B/C 以及 B/C 如何链接到 Meta

解决方法

调用 type('B',(),{}) 而不是 Meta,因为 B.class 是类型。

正如你后面提到的,它不是。

>>> class Meta(type): pass
...
>>> class A(metaclass=Meta): pass
...
>>> class B(A): pass
...
>>> type(B)
<class '__main__.Meta'>
>>>

我的问题是 Python 如何创建 B/C 类以及 B/C 如何链接到 Meta?

如果X类继承了Y类,则X的元类与Y的元类相同。您可以在 data model documentation 上找到详细信息。

来自文档:

类定义的适当元类被确定为 如下:

如果没有给出基类和明确的元类,则使用 type();

如果给出了一个显式元类并且它不是 type() 的实例, 然后直接作为元类使用;

如果 type() 的实例作为显式元类或基类给出 定义,然后使用最派生的元类。

从显式指定的元类中选择最派生的元类 所有的元类(如果有)和元类(即类型(cls)) 指定的基类。最派生的元类是一个 所有这些候选元类的子类型。如果没有 候选元类满足该标准,然后类定义 会因 TypeError 而失败。

,

所以 --- 一个有点令人困惑的问题,可以回答,有些简化 只需在交互模式下运行一些示例即可。

但首先,当您声明时:

type.__call__(...) in turn run two other methods (a __new__ and a __init__).

这是对发生的事情的简化。

当我们创建新类时,就像在解析类语句 class A: 时一样,type.__call__ 会被调用。但是这个调用是在 Meta 本身的 class 中搜索的。也就是说,“Meta”的“元类” - 默认情况下为 type

忍耐一下: 当我们谈论一个没有自定义元类的普通类 E,并且您通过执行 E() 创建实例时 - Python 在 __call__ 是实例的类中搜索 E 方法: 即它的元类。因为它是类型,所以 type.__call__ 被调用。正如您所说,type.__call__ 调用 __new____init__ 方法,但不仅限于元类:它在任何对象实例化中编排这些调用 - 在任何对象中都使用完全相同的机制Python 中的对象实例化:普通对象和类:



In [178]: class MetaMeta(type): 
     ...:     def __call__(metacls,*args,**kw): 
     ...:         print("Now at the meta-meta class") 
     ...:         return super().__call__(*args,**kw) 
     ...:                         

In [179]: class EmptyMeta(type,metaclass=MetaMeta): 
     ...:     def __call__(cls,**kw): 
     ...:         print("At the metaclass __call__") 
     ...:         return super().__call__(*args,**kw) 
     ...:          
     ...:      
     ...:                         

In [180]: class A(metaclass=EmptyMeta): 
     ...:     pass 
     ...:                         
Now at the meta-meta class

In [181]: a = A()                 
At the metaclass __call__

In [182]: class Direct(metaclass=MetaMeta): pass                     

In [183]: Direct()                
Now at the meta-meta class
Out[183]: <__main__.Direct at 0x7fa66bc72c10>


所以,简而言之:在创建类A时,它是Meta的一个实例,调用Meta类的__call__方法。这将在元类 Meta 中调用 __init____new__。如果没有定义,普通的属性查找会在 Meta 的超类中调用这些方法,而这恰好是“类型”。

现在,继续您的问题:当从具有自定义元类的类(例如您的 B 类)继承时,Python 将其超类中派生最多的元类作为自己的元类,而不是 {{1} }.无需显式声明自定义元类。实际上,这就是需要元类而不仅仅是类装饰器的原因:它们只影响声明它们的类,而对进一步的子类没有影响。

type

即使在显式调用 In [184]: class B(A): pass Now at the meta-meta class In [185]: B() At the metaclass __call__ Out[185]: <__main__.B at 0x7fa6682ab3a0> In [186]: B.__class__ Out[186]: __main__.EmptyMeta 而不是 type 语句中,派生类的元类也将是超类的元类。但是请注意,在这种情况下,我们将对“metameta”类的调用硬编码为 class 并且“元类的自定义元类”被忽略:

type.__new__

如果您想以编程方式创建一个具有自定义“元元类”的类(上帝禁止除了学习目的以外的任何其他用途), In [187]: C = type("C",(A,),{}) In [188]: C() At the metaclass __call__ Out[188]: <__main__.C at 0x7fa653cb0d60> 模块中有一个特殊调用可以执行此操作:

types

总结一下,如果一个类的超类有不同的元类,Python 将完全拒绝创建一个类。这在“现实世界”代码中有些常见,当人们尝试在一些带有 ORM 的框架中创建带有基类的抽象类(使用自定义元类)时,通常也有一个自定义元类:


In [192]: import types            

In [193]: D = types.new_class("D",{})                         
Now at the meta-meta class

In [194]: D()                     
At the metaclass __call__
Out[194]: <__main__.D at 0x7fa6682959a0>

可以通过生成继承自的派生元类来修复 两个祖先分支中的元类(这要求两个元类 表现良好,使用 In [203]: class Meta1(type): pass In [204]: class Meta2(type): pass In [205]: class A(metaclass=Meta1): pass In [206]: class B(metaclass=Meta2): pass In [207]: class C(A,B): pass --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-207-1def53cc27f4> in <module> ----> 1 class C(A,B): pass TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases 而不是对 super() 的硬编码调用 - 但 维护良好且流行的框架就是这种情况):

type

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-