草庐IT

python - 元类的继承

coder 2023-08-18 原文

在这个众所周知的answer这解释了 Python 中的元类。它提到 __metaclass__ 属性不会被继承。

但事实上,我在 Python 中尝试过:

class Meta1(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta1"
        return type.__new__(cls, clsname, bases, dct)

# "Using Meta1" printed
class Foo1:
    __metaclass__ = Meta1

# "Using Meta1" printed
class Bar1(Foo1):
    pass

正如预期的那样,FooBar 都使用 Meta1 作为元类并按预期打印字符串。

但在下面的示例中,当返回 type(...) 而不是 type.__new__(...) 时,元类不再被继承:

class Meta2(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta2"
        return type(clsname, bases, dct)

# "Using Meta2" printed
class Foo2:
    __metaclass__ = Meta2

# Nothing printed
class Bar2(Foo2):
    pass

检查 __metaclass____class__ 属性,我可以看到:

print Foo1.__metaclass__ # <class '__main__.Meta1'>
print Bar1.__metaclass__ # <class '__main__.Meta1'>
print Foo2.__metaclass__ # <class '__main__.Meta2'>
print Bar2.__metaclass__ # <class '__main__.Meta2'>

print Foo1.__class__ # <class '__main__.Meta1'>
print Bar1.__class__ # <class '__main__.Meta1'>
print Foo2.__class__ # <type 'type'>
print Bar2.__class__ # <type 'type'>

总结:

  1. __metaclass____class__ 都会继承自基类。

  2. Meta2定义的创建行为将用于Foo2,虽然Foo2.__class__实际上是type.

  3. Bar2中的__metaclass__属性是Meta2,但是Bar2的创建行为不是做作的。换句话说,Bar2 使用 type 作为其“真正的”元类,而不是 Meta2

这些观察结果让我对 __metaclass__ 的继承机制有点模糊。

我的猜测是:

  1. 当直接将类(例如Meta1)分配给另一个类'Foo1'的__metaclass__属性时,它是__metaclass__属性生效。

  2. 当子类在定义时没有显式设置__metaclass__属性。基类的 __class__ 属性而不是 __metaclass__ 属性将决定子类的“真实”元类。

我的猜测正确吗? Python如何处理元类的继承?

最佳答案

你在猜测很多,而 Python 的极简主义和“特殊情况还不足以打破规则”。指令,让它更容易理解。

在 Python2 中,类主体中的 __metaclass__ 属性在类创建时用于调用该类将成为的“类”。通常它是名为 type 的类。需要澄清的是,那一刻是在解析器解析了类主体之后,在编译器将其编译为代码对象之后,并且在程序运行时实际运行之后,并且只有 __metaclass__ 是显式的在该类(class)机构中提供。

所以让我们检查一下这样的情况:

class A(object):
    __metaclass__ = MetaA

class B(A):
    pass

A 的主体中有 __metaclass__ - MetaA 被调用而不是 type 以使其成为“类”目的”。 B 的主体中没有 __metaclass__。创建后,如果您只是尝试访问 __metaclass__ 属性,它是一个与其他任何属性一样的属性,它将是可见的,因为 Python 将从父类(super class) A 中获取它。如果您检查 A.__dict__,您将看到 __metaclass__,如果您检查 B.__dict__,则不会。

创建 B 时,A.__metaclass__ 属性根本未使用。如果在声明 B 之前在 A 中更改它,仍将使用与 A 相同的元类 - 因为 Python 确实使用父类的类型作为缺少显式 __metaclass__ 声明的元类。

举例说明:

In [1]: class M(type): pass

In [2]: class A(object): __metaclass__ = M

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'>

In [4]: class B(A): pass

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'>

In [6]: A.__metaclass__ = type

In [8]: class C(A): pass

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C))
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'>

此外,如果您尝试通过调用 type 而不是使用带有 class 语句的主体来创建类,__metaclass__也只是一个普通的属性:

In [11]: D = type("D", (object,), {"__metaclass__": M})

In [12]: type(D)
type

到目前为止的总结:Python 2 中的 __metaclass__ 属性只有在作为 class block 语句。它是一个普通属性,之后没有特殊属性。

Python3 都摆脱了这种奇怪的“__metaclass__ 属性现在不好”,并允许通过更改语法以指定元类来进一步自定义类主体。 (就像在 class 语句本身上声明它是一个“metaclass named parameter”一样)

现在,对于引起您怀疑的第二部分:如果在元类的 __new__ 方法中调用 type 而不是 type.__new__,Python 无法“知道”正在从派生元类调用 type。当您调用 type.__new__ 时,您将 cls 属性作为其第一个参数传递给您的元类的 __new__ 本身由运行时传递:这就是将生成的类标记为 type 的子类的实例。 这就像继承适用于 Python 中的任何其他类一样 - 所以这里“没有特殊行为”:

所以,找出不同之处:

class M1(type):
    def __new__(metacls, name, bases, attrs):
         cls = type.__new__(metacls, name, bases, attrs)
         # cls now is an instance of "M1"
         ...
         return cls


class M2(type):
    def __new__(metacls, name, bases, attrs):
         cls = type(name, bases, attrs)
         # Type does not "know" it was called from within "M2"
         # cls is an ordinary instance of "type"
         ...
         return cls

在交互提示中可以看到:

In [13]: class M2(type):
   ....:     def __new__(metacls, name, bases, attrs):
   ....:         return type(name, bases, attrs)
   ....:     

In [14]: class A(M2): pass

In [15]: type(A)
Out[15]: type

In [16]: class A(M2): __metaclass__ = M2

In [17]: A.__class__, A.__metaclass__
Out[17]: (type, __main__.M2)

(请注意,元类 __new__ 方法的第一个参数是元类本身,因此更恰本地命名为 metacls 而不是 cls 在您的代码中,并且在很多代码中“在野外”)

关于python - 元类的继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37410692/

有关python - 元类的继承的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. Ruby 元类 : why three when defined singleton methods? - 2

    让我们计算MRI范围内的类别:defcount_classesObjectSpace.count_objects[:T_CLASS]endk=count_classes用类方法定义类:classAdefself.foonilendend然后运行:putscount_classes-k#=>3请解释一下,为什么是三个? 最佳答案 查看MRI代码,每次你创建一个Class时,在Ruby中它是Class类型的对象,ruby会自动为这个新类创建“元类”类,这是另一个单例类型的Class对象。C函数调用(class.c)是:rb_define

  3. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  4. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  5. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  6. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

随机推荐