【问题标题】:Change class parent dynamically from attributes python 3从属性python 3动态更改类父级
【发布时间】:2017-11-05 02:43:46
【问题描述】:

我希望新类根据创建实例时给定的属性从不同的父类动态继承。到目前为止,我已经尝试过这样的事情:

class Meta(type):
    chooser = None
    def __call__(cls, *args, **kwars):
        if kwargs['thingy'] == 'option':
            Meta.choose = option
        return super().__call__(*args, **kwargs)

    def __new__(cls, name, parents, attrs):
        if Meta.choose == option:
            bases = (parent1)
        return super().__new__(cls, name, parents, attrs)

它不起作用,有没有一种方法可以根据实例的参数之一,动态选择类的父级?

【问题讨论】:

    标签: python-3.x class metaclass


    【解决方案1】:

    首先,让我们修复代码中的一个小错误,然后深入研究“真正的问题”:bases 参数需要是一个元组。当您执行 bases=(option) 时,右侧不是元组 - 它只是一个带括号的表达式,将被解析并作为非元组 option 传递。

    当您需要为基础创建元组时,将其更改为 bases=(option,)

    第二个错误更具概念性,这可能是您没有让它在各种尝试中起作用的原因:元类的__call__ 方法不是人们通常会摆弄的东西。为了简短起见,__class____call__ 方法被调用来协调该类实例的 __new____init__ 方法的调用 - 这是由 Python 自动生成的,它是具有此机制的type 中的__call__。当您将其转置到您的元类时,您可能会意识到当您的元类本身的 __new____init__ 方法即将被调用时(当定义了一个类)。换句话说 - 使用的 __call__ 位于“元元”类(同样是类型)上。

    您编写的 __call__ 方法将在您的自定义类的实例被创建时使用(这就是您的意图),并且不会影响类的创建,因为它不会调用元类' __new__ - 只是类 __new__ 本身。 (这不是你想要的)。

    所以,你需要的是,从__call__ 内部不要使用你收到的相同参数调用super().__call__:这会将cls 传递给type 的调用,cls 的基础是当元类 __new__ 运行时就被烘焙了——它只会在类主体本身被声明时发生。

    您必须在此__call__ 中动态创建一个新类,或使用预先填写的表之一,然后他们将该动态创建的类传递给type.__call__

    但是 - 归根结底,我们可以意识到所有这些都可以通过 factory 函数来完成,因此没有必要为此创建这种超级复杂的元类机制- 其他 Python 工具,例如 linter 和静态分析器(嵌入在您或您的同事可能正在使用的 IDE 中)可能会更好地使用它。

    使用工厂函数的解决方案:

    def factory(cls, *args, options=None, **kwargs):
       
       if options == 'thingy': 
            cls = type(cls.__name__, (option1, ), cls.__dict__)
       elif options = 'other':
            ...
       return cls(*args, **kwargs)
    

    如果你不想在每次调用时都创建一个新类,但想共享几个具有公共基础的预先存在的类,只需创建一个缓存字典,并使用字典的 setdefault 方法:

    class_cache = {}
    
    def factory(cls, *args, options=None, **kwargs):
       
       
       if options == 'thingy': 
            cls = class_cache.setdefault((cls.__name__, options),
                  type(cls.__name__, (option1, ), cls.__dict__))
       elif options = 'other':
            ...
       return cls(*args, **kwargs)
    

    (如果键(名称,选项)尚不存在,setdefault 方法会将第二个参数存储在 dict 中)。

    使用元类:

    更新

    早餐后 :-) 我想出了这个: 让你的元类__new__ 在创建的类本身上注入一个__new__ 函数,该函数将创建一个新类,动态地具有所需的基础,或者为相同的选项使用缓存的类。但与其他示例不同的是,使用元类将原始参数注释到类创建以创建派生类:

    parameter_buffer = {}
    derived_classes = {}
    
    class Meta:
        def __new__(metacls, name, bases, namespace):
            cls = super().__new__(metacls, name, bases, namespace)
            parameter_buffer[cls] = (name, bases, namespace) 
            def __new__(cls, *args, option=None, **kwargs):
                if option is None:
                    return original_new(cls, *args, **kwargs)
                name, bases, namespace = parameter_buffer[cls]
                if option == 'thingy':
                    bases = (option1,)
                elif option== 'thingy2':
                    ...
                if not (cls, bases) in derived_classes:
                    derived_classes[cls, bases] = type(name, bases, namespace)
                return derived_classes[cls, bases](*args, **kwargs)
                
            cls.__new__ = __new__
            return cls
    

    为了使示例简短,这只是覆盖使用此元类的类上的任何显式__new__ 方法。此外,以这种方式创建的派生类本身并不具有相同的能力,因为它们是调用type 创建的,并且元类在此过程中被丢弃。这两件事都可以通过编写更仔细的代码来解决,但在这里作为示例会变得复杂。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-24
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 2019-08-11
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多