如何将参数传递给python中的嵌套类

Posted

技术标签:

【中文标题】如何将参数传递给python中的嵌套类【英文标题】:how to pass arguments into a nested class in python 【发布时间】:2021-11-18 02:39:25 【问题描述】:

我有以下型号:

def get_my_model(name1, name2):
    class MyModel(Model):
       class Meta: 
             table1 = name1
             table2 = name2
    return MyModel
  
     

我试图在没有函数的情况下编写它,并希望将参数 name1name2 直接传递给 Meta 类,但我不确定如何。我尝试使用构造函数,但不知何故不起作用。

class MyModel(Model):
       def __init__(self, name1, name2):
             self.name1 = name1
             self.name2 = name2

       class Meta: 
             table1 = self.name1
             table2 = self.name2 

我也尝试在元类上设置构造函数,但也没有用。有人有想法吗?

【问题讨论】:

这里没有元类...您的意思是嵌套在您的MyModel 类中名为Meta 的类吗? 为什么你认为MyModel 中的__init__ 与某个嵌套类Meta 完全相关?我假设这是 django 相关的?除此之外,Python 中不经常使用嵌套类。无论如何,您所期望的行为并不完全清楚 它也可以被破解以使用元类,但它与元类相关;如果您无论如何都想使用元类,我建议您阅读this great post 了解它们。 【参考方案1】:

TL;DR:

来自MyModel 的任何可访问变量或来自名为Meta 的类的任何更高范围的变量,已经定义 修改ModelBase的元类(修补文件或继承自)

这不是元类!它只是其他类范围内的一个类,名为Meta。发生的情况是,一旦执行环境开始构建,Python 就会有单独的上下文/范围:

interpreter global(启动解释器后获得的原始命名空间,globals()

然后是全局命名空间中的嵌套:

模块/文件 类 功能 也许还有一些其他的

您不能将参数传递给Meta 类,因为它只是在那里声明的。它没有被调用。与此并行的是将参数从模块范围传递给类声明:

# module.py
class MyClass:
    value = <how?>

一旦找到调用的地方,那么就可以通过修改调用函数来注入参数了。

class Main:
    class Meta:
        def __init__(self, *args, **kwargs):
            print("Meta", args, kwargs)

    def __init__(self, name):
        Main.Meta(name)

print(Main(123))

如果我不显式调用Main.Meta(),则不会实例化此示例中的Meta 类。

对于 Django,Meta 类是通过 getattr() 为模型类 here 提取的,因此您需要使用 super()/copy-paste 定位 ModelBase.__new__() 来修改函数以便它接受自定义参数,或者将它们作为类变量传递(在 Django / DRF 中主要是如何完成的)。

class Main:
    class Meta:
        class_var = 123

从 Django 中的 implementation of Model class 判断,您可能可以交换 metaclass 值,但我不确定继承,因为在声明使用元类的类时会执行元类的 __new__()

# the original ModelBase from class Model(metaclass=ModelBase)
class Meta(type):
    def __new__(cls, *_, **__):
        print("Hello")
        return cls

class MyMeta(Meta):
    def __new__(cls, *_, **__):
        print("Hi")
        # implement your stuff here or copy-paste + patch
        return cls

class Model(metaclass=Meta):
    pass

class CustomModel(metaclass=MyMeta):
    pass

class CustomModelWithInheritance(Model, metaclass=MyMeta):
    pass

对于元类检查:

this question this essay docs its tests

关于self:命名本身并不重要,也不会在你使用它的地方起作用,因为self只是一个隐式传递的类实例到方法(一个引用类的函数实例):

class MyClass:
    def func(self_or_other_name):
        print(self_or_other_name)
MyClass().func()

在元类中创建类时,__new__() 方法中的cls 参数的行为方式相同,即它是对类实例的引用(a命名空间中的类声明),“描述”是创建它的元类。

cls = type("MyClass", (), )  # create an instance of "type"
cls  # <class '__main__.MyClass'>
# "cls" is class and an instance at the same time

您可以用来引用类范围的唯一“特殊”变量是locals() + 在类范围内定义为变量的任何内容 + 更高范围内的任何内容,无论它来自嵌套类、模块还是其他:

编辑:

所以对于类变量来说这是一个错误,这是正确的解释。对于您的情况,它可能是一个函数或元类补丁,因为我们在这里处理的是寻找“鸡和蛋”问题的开始。我们可以通过从上方查看 - 在上层类的定义完成之前引入函数范围并设置值 - 或从创建过程(元类)内部查看。

我们不能使用类变量,因为我们仍在定义类的过程中。我们不能使用__init__ 引用类(或Meta 嵌套类)在其中注入值,因为我们已经处于元类'__new__()方法已被执行,因此您在此处设置的任何内容都可能在您的情况下使用,但在创建 MyModel 类时不会出现。

class MyModel(Model):
    def __init__(self, name):
        MyModel.name = name  # MyModel not defined yet
        # but we can use self.__class__ in the post-poned execution
        # once MyModel is defined (case when calling __init__())
    # we haven't even started to define the Meta nested class

    # starting to define Meta nested class
    class Meta:
        # MyModel not defined yet because the code block
        # of MyModel class isn't finished yet
        table = MyModel.name
        # can't reference because it doesn't exist yet,
        # same applies for Meta, the only thing you have now is
        # the __qualname__ string

但是!

from pprint import pprint

class RealMetaClass(type):
    def __new__(cls, *_, **__):
        # create a new class - "Main" because it uses "metaclass" kwarg
        new_class = super().__new__(cls, *_, **__)
        # once we have the "Main" class, we can reference it the same way
        # like *after* we define it the normal way
        nested_meta = getattr(new_class, "NestedClassNamedMeta")

        # ~= pprint(getattr(Main, "NestedClassNamedMeta"))
        pprint(("in metaclass", nested_meta))
        pprint(("in metaclass", dir(nested_meta)))
        pprint(("in metaclass", vars(nested_meta)))
        return new_class


class CustomMetaClass(RealMetaClass):
    def __new__(cls, *_, **__):
        new_class = super().__new__(cls, *_, **__)
        nested_meta = getattr(new_class, "NestedClassNamedMeta")
        # set a new class variable without affecting previous __new__() call
        new_class.custom_thing = getattr(nested_meta, "custom_thing", None)
        # do my stuff with custom attribute, that's not handled by Django
        return new_class


class Main(metaclass=RealMetaClass):
    pprint("defining Main class")

    def __init__(self, name):
        pprint(("in instance", self.__class__.NestedClassNamedMeta.name))
        # works, because it's called after the Main class block
        # is finished and executed i.e. defined
        self.__class__.NestedClassNamedMeta.new_value = "hello"

    class NestedClassNamedMeta:
        name = "John"
        custom_thing = "custom"


class CustomMain(Main, metaclass=CustomMetaClass):
    class NestedClassNamedMeta:
        name = "John"
        custom_thing = "custom"

instance = Main("Fred")
# custom_thing is ignored, not added to instance.__class__
pprint(("after init", vars(instance.__class__), vars(instance.NestedClassNamedMeta)))

instance = CustomMain("Fred")
# custom_thing is processed, added to instance.__class__
pprint(("after init", vars(instance.__class__), vars(instance.NestedClassNamedMeta)))

【讨论】:

woaaaaahhhh,非常感谢您的长篇回答! 欢迎您,希望对您有所帮助!如果您有任何机会找到CustomModelWithInheritance(Model, metaclass=MyMeta) 的方法,请随时 ping 或编辑。我必须以某种方式工作,但我今天可能太累了,错过了一些基本的傻事:D 另外,根据我的观察,Django 和 DRF 中的Meta 只是一本花哨的字典,所以你不需要使用自定义函数来准备模型,或者换句话说,核心开发人员只是将逻辑移动到更抽象的层,以使其更加用户友好和自动化。 @Tom 请在编辑后再次检查,类变量将不起作用。或者至少不是以您期望的方式工作(在ModelBase 元类中具有值)。 @Tom 也是,重新继承,正如我所想,愚蠢的事情,我忘记添加 (type) 所以类继承自 object 而不是类型,因此 MRO 无聊。跨度>

以上是关于如何将参数传递给python中的嵌套类的主要内容,如果未能解决你的问题,请参考以下文章

如何将参数传递给 Apache Apex 中的 application.java 类?

异步初始化时将参数传递给python类

如何将参数传递给 Python 模块中的主函数?

如何正确地将参数传递给 Django 中的基于类的视图实例?

将参数传递给 Docker 容器中的 Python argparse

有没有一种方法可以将宏名称作为参数传递给嵌套宏,而不会在扩展最外层的宏时扩展它们?