使用Metaclass的动态类定义

发布于 2025-02-03 01:11:16 字数 845 浏览 2 评论 0原文

我试图动态创建类定义,如下所示。

class SomeMetaclass(type):
  def __new__(cls, clsname, bases, clsdict):
    print("Using this metaclass")
    return super().__new__(cls, clsname, bases, clsdict)

SomeClassFoo = SomeMetaclass('SomeClassFoo', (), {})

# Similar to the following
# SomeClassFoo = type('SomeClassFoo', (), {})

但是,我认为我在这里错过了一些东西。虽然上面的示例无法做我期望的事情,但以下方法有效:


class SomeClassBar(metaclass=SomeMetaclass):
  pass

但是,当我在两个示例中运行type(someclassfoo)时,我都会得到类somemetaclass ?


# The following statement is true
type(SomeClassFoo) is type(SomeClassBar)

准备/新的方法在使用前一个方法定义类时未调用?

I am trying to create class definitions dynamically as follows.

class SomeMetaclass(type):
  def __new__(cls, clsname, bases, clsdict):
    print("Using this metaclass")
    return super().__new__(cls, clsname, bases, clsdict)

SomeClassFoo = SomeMetaclass('SomeClassFoo', (), {})

# Similar to the following
# SomeClassFoo = type('SomeClassFoo', (), {})

However, I think I am missing something here. While the above example doesn't do what I would expect it to, the following method works:


class SomeClassBar(metaclass=SomeMetaclass):
  pass

However, when I run type(SomeClassFoo) in both the examples, I get class SomeMetaclass?


# The following statement is true
type(SomeClassFoo) is type(SomeClassBar)

Are the prepare/new methods not called when the class is defined using the former method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

油焖大侠 2025-02-10 01:11:17

所有人都按预期工作。问题当前被认为是低质量,因为您可能忘记在摘要上放置__准备__实现,但是从评论中,我想您正在做类似的事情:

class SomeMetaclass(type):

    @classmethod
    def __prepare__(mcls, name, bases):
        print(f"running __prepare__ for {name}")
        return super().__prepare__(name, bases)
        
    def __new__(mcls, name, bases, clsdict):
        print(f"running  __new__  for {name}")
        return super().__new__(mcls, name, bases, clsdict)

SomeClass1 = SomeMetaclass('SomeClass1', (), {})

class SomeClass2(metaclass=SomeMetaclass):
    pass

运行它,输出为::

running  __new__  for SomeClass1
running __prepare__ for SomeClass2
running  __new__  for SomeClass2

正如这里可见的那样,“ someclass1”创建不调用__准备__,正确:类名称空间已经传递到呼叫中,为第三个参数,{},没有身体要跑。

当一个人想以这样的程序方式使用元类(包括允许Python使用内部查找机制来获取正确的__准备__方法,并手动使用它, types 那。有两种方式,一个正在打电话
types.prepare_class它将找出要调用的正确元类(如果除了您的显式“ Metaclass”参数外,您的“基础”还包括也具有元元素的类,一个要呼叫的元素是最派生的元类)) ,运行其__准备__方法,然后返回元素,由__准备__创建的命名空间以及任何剩余的关键字参数以调用元素。然后,您可以在看到合适的情况下调用返回的元素:(

In [32]: metaclass, classdict, kwds  = types.prepare_class("SomeClass3", (), {"metaclass": SomeMetaclass})
running __prepare__ for SomeClass3

In [33]: metaclass("SomeClass3", (), classdict, **kwds)
running  __new__  for SomeClass3
Out[33]: __main__.SomeClass3


请注意,如果您想要的东西,则应该在两个呼叫之间填充“ classDict”)。

另一种方法是调用types.new_class:它几乎可以做到这一点,但是它不会为您返回类创建信息,而是将metaclass .__新的__ new __直接调用。如果要将任何内容插入新的类名称空间中,则必须将其传递给第四个参数,并带有一个回调,将其命名空间作为参数,它必须填充它并将其返回:

In [34]: SomeClass3 = types.new_class("SomeClass3", (),  {"metaclass": SomeMetaclass})
running __prepare__ for SomeClass3
running  __new__  for SomeClass3

In [35]: def fill_classdict(classdict):
    ...:     classdict["attribute"] = 23
    ...: 

In [36]: SomeClass4 = types.new_class("SomeClass4", (),  {"metaclass": SomeMetaclass}, fill_classdict)
running __prepare__ for SomeClass4
running  __new__  for SomeClass4

In [37]: SomeClass4.attribute
Out[37]: 23


All is working as expected. The question is currently regarded as low quality, as you likely forgot to put __prepare__ implementation on your snippets, but from the comments, I imagine you are doing something like this:

class SomeMetaclass(type):

    @classmethod
    def __prepare__(mcls, name, bases):
        print(f"running __prepare__ for {name}")
        return super().__prepare__(name, bases)
        
    def __new__(mcls, name, bases, clsdict):
        print(f"running  __new__  for {name}")
        return super().__new__(mcls, name, bases, clsdict)

SomeClass1 = SomeMetaclass('SomeClass1', (), {})

class SomeClass2(metaclass=SomeMetaclass):
    pass

Upon running it, the output is:

running  __new__  for SomeClass1
running __prepare__ for SomeClass2
running  __new__  for SomeClass2

As is visible here, "SomeClass1" creation does not call __prepare__, rightly: the class namespace is already passed into the call as the third parameter which is {}, and there is no body to be run.

When one wants to use metaclasses in a programatic way like this, including allowing Python to use the internal lookup mechanisms to get the correct __prepare__ method, and use it manually, there are helpers in the types module that provide the means for that. There are two ways, one is calling
types.prepare_class which will find out the correct metaclass to call (if besides your explicit "metaclass" parameter, your "bases" include classes which also have metaclasses, the one to call is the most derived metaclass), run its __prepare__ method, and return you the metaclass, the namespace created by __prepare__ and any remainder keyword arguments to call the metaclass. You can then call the returned metaclass as you see fit:

In [32]: metaclass, classdict, kwds  = types.prepare_class("SomeClass3", (), {"metaclass": SomeMetaclass})
running __prepare__ for SomeClass3

In [33]: metaclass("SomeClass3", (), classdict, **kwds)
running  __new__  for SomeClass3
Out[33]: __main__.SomeClass3


(note that between both calls you are supposed to populate "classdict" if you want something in there).

The other way is to call types.new_class: it does almost the samething, but instead of returning the class creation information for you, it will call the metaclass.__new__ method directly. If you want to insert anything into the new class namespace, you have to pass it a 4th parameter, with a callback that will take the namespace as a parameter, it has to populate it, and return it:

In [34]: SomeClass3 = types.new_class("SomeClass3", (),  {"metaclass": SomeMetaclass})
running __prepare__ for SomeClass3
running  __new__  for SomeClass3

In [35]: def fill_classdict(classdict):
    ...:     classdict["attribute"] = 23
    ...: 

In [36]: SomeClass4 = types.new_class("SomeClass4", (),  {"metaclass": SomeMetaclass}, fill_classdict)
running __prepare__ for SomeClass4
running  __new__  for SomeClass4

In [37]: SomeClass4.attribute
Out[37]: 23


~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文